An open protocol to allow the creation and consumption of queryable and interoperable RESTful APIs in a simple and standard way.
Tutorials, libraries, and everything else you need to build your first OData API today
Start realizing the benefits OData can bring to your business from the OData ecosystem
OASIS approved industry standard based on best practices of building RESTful APIs
OData (Open Data Protocol) is an OASIS standard that defines the best practice for building and consuming RESTful APIs. OData helps you focus on your business logic while building RESTful APIs without having to worry about the approaches to define request and response headers, status codes, HTTP methods, URL conventions, media types, payload formats and query options etc. OData also guides you about tracking changes, defining functions/actions for reusable procedures and sending asynchronous/batch requests etc. Additionally, OData provides facility for extension to fulfill any custom needs of your RESTful APIs.
OData RESTful APIs are easy to consume. The OData metadata, a machine-readable description of the data model of the APIs, enables the creation of powerful generic client proxies and tools. Some of them can help you interact with OData even without knowing anything about the protocol. The following 6 steps demonstrate 6 interesting scenarios of OData consumption across different programming platforms. But if you are a non-developer and would like to simply play with OData, XOData is the best start for you.
As the REST principles go, "Everything is a Resource". As a simple start, let's see how resources can be retrieved from the OData RESTful APIs. The sample service used is the TripPin service which simulates the service of an open trip management system. Our friend, Russell White, who has formerly registered TripPin, would like to find out who are the other people in it.
GET http://services.odata.org/v4/TripPinServiceRW/People HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0var context = new DefaultContainer(new Uri("http://services.odata.org/v4/TripPinServiceRW/"));
var people = context.People.Execute();var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var people = await client.For<People>().FindEntriesAsync();var x = ODataDynamic.Expression;
var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var people = await client.For(x.People).FindEntriesAsync();var serviceRoot = 'http://services.odata.org/V4/TripPinServiceRW/';
var headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
var request = {
requestUri: serviceRoot + 'People',
method: 'GET',
headers: headers,
data: null
};
odatajs.oData.request(
request,
function (data, response) {
var people = data.value;
},
function (err) {
alert('Fail: ' + err.Message);
}
);auto service_context = std::make_shared<DefaultContainer>(U("http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
auto people = service_context->create_people_query()->execute_query().get();var http = require('http');
var serviceRoot = 'http://services.odata.org/v4/TripPinServiceRW/';
getURL(serviceRoot + 'People');
function getURL(url) {
var body = '';
http.get(url, function (response) {
response.on('data', function (chunk) {
body+=chunk;
});
response.on('end', function () {
console.log(body);
});
}).on('error', function(e) {
console.log('ERROR: ' + e.message);
});
}Want to contribute code snippet for another platform or suggest changes to this content? You can edit and submit changes to "Understanding OData in 6 steps" on its Github repository.
REST principles also say, that every resource is identified by a unique identifier. OData also enables defining key properties of a resource and retrieving it using the keys. In this step, Russell wants to find the information about himself by specifying his username as the key.
GET http://services.odata.org/v4/TripPinServiceRW/People('russellwhyte') HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0var context = new DefaultContainer(new Uri("http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
var person = context.People.ByKey(userName: "russellwhyte").GetValue();var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var person = await client.For<People>().Key("russellwhyte").FindEntryAsync();var x = ODataDynamic.Expression;
var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var person = await client.For(x.People).Key("russellwhyte").FindEntryAsync();
Assert.Equal("russellwhyte", person.UserName);var serviceRoot = 'http://services.odata.org/V4/TripPinServiceRW/';
var headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
var request = {
requestUri: serviceRoot + 'People("russellwhyte")',
method: 'GET',
headers: headers,
data: null
};
odatajs.oData.request(
request,
function (data, response) {
var russell = data;
},
function (err) {
alert('Fail: ' + err.Message);
}
);auto service_context = std::make_shared<DefaultContainer>(U("http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
auto people = service_context->create_people_query()->key(U("russellwhyte"))->execute_query().get();var http = require('http');
var serviceRoot = 'http://services.odata.org/v4/TripPinServiceRW/';
getURL(serviceRoot + 'People("russellwhyte")');
function getURL(url) {
var body = '';
http.get(url, function (response) {
response.on('data', function (chunk) {
body+=chunk;
});
response.on('end', function () {
console.log(body);
});
}).on('error', function(e) {
console.log('ERROR: ' + e.message);
});
}Want to contribute code snippet for another platform or suggest changes to this content? You can edit and submit changes to "Understanding OData in 6 steps" on its Github repository.
As an architecture that's built on top of the current features of the Web, RESTful APIs can also support query strings. For that, OData defines a series of system query options that can help you construct complicated queries for the resources you want. With the help of that, our friend Russell can find out the first 2 persons in the system who have registered at least one trip that costs more than 3000, and only display their first name and last name.
GET http://services.odata.org/v4/TripPinServiceRW/People?$top=2 & $select=FirstName, LastName & $filter=Trips/any(d:d/Budget gt 3000) HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0var context = new DefaultContainer(new Uri("http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
var people = context.People.Where(c => c.Trips.Any(d => d.Budget > 3000)).Take(2).Select(c => new {c.FirstName, c.LastName});var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var people = await client.For<People>()
.Filter(x => x.Trips.Any(y=> y.Budget > 3000))
.Top(2)
.Select(x => new {x.FirstName, x.LastName})
.FindEntriesAsync();var x = ODataDynamic.Expression;
var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var people = await client
.For(x.People)
.Filter(x.Trips.Any(x.Budget > 3000))
.Top(2)
.Select(x.FirstName, x.LastName)
.FindEntriesAsync() as IEnumerable<dynamic>;var serviceRoot = 'http://services.odata.org/V4/TripPinServiceRW/';
var headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
var request = {
requestUri: serviceRoot + 'People?$top=2 & $filter=Trips/any(d:d/Budget gt 3000)',
method: 'GET',
headers: headers,
data: null
};
odatajs.oData.request(
request,
function (data, response) {
var filtedPeople = data.value;
var FirstName = filtedPeople[0].FirstName;
},
function (err) {
alert('Fail: ' + err.Message);
}
);auto service_context = std::make_shared<DefaultContainer>(U("http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
auto people = service_context->create_people_query()->filter(U("Trips/any(d:d/Budget gt 3000)"))->select(U("FirstName, LastName"))->top(2)->execute_query().get();var http = require('http');
var serviceRoot = 'http://services.odata.org/v4/TripPinServiceRW/';
getURL(serviceRoot + 'People?$top=2 & $select=FirstName, LastName & $filter=Trips/any(d:d/Budget gt 3000)');
function getURL(url) {
var body = '';
http.get(url, function (response) {
response.on('data', function (chunk) {
body+=chunk;
});
response.on('end', function () {
console.log(body);
});
}).on('error', function(e) {
console.log('ERROR: ' + e.message);
});
}Want to contribute code snippet for another platform or suggest changes to this content? You can edit and submit changes to "Understanding OData in 6 steps" on its Github repository.
REST principles require the using of simple and uniform interfaces. With that regard, OData clients can expect unified interfaces of the resources. The stateless transfer of representations in REST are carried out by using different HTTP methods in the requests. After having gone through the first 3 steps, Russell thinks the system is useful. He wants to add his best friend Lewis to the system. He finds out that all he needs to do is to send a POST request containing a JSON representation of Lewis' information to the same interface from which he requested the people information.
POST http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/People HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Length: 428
Content-Type: application/json
{
"UserName":"lewisblack",
"FirstName":"Lewis",
"LastName":"Black",
"Emails":[
"lewisblack@example.com"
],
"AddressInfo":[
{
"Address":"187 Suffolk Ln.",
"City":{
"CountryRegion":"United States",
"Name":"Boise",
"Region":"ID"
}
}
],
"Gender":"Male",
"Concurrency":635519729375200400
}var context = new DefaultContainer(new Uri("http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
var lewis = new Person()
{
UserName = "lewisblack",
FirstName = "Lewis",
LastName = "Black",
Emails = new ObservableCollection<string>() {"lewisblack@example.com"},
AddressInfo =
new ObservableCollection<Location>()
{
new Location()
{
Address = "187 Suffolk Ln.",
City = new City() {CountryRegion = "United States", Name = "Boise", Region = "ID"}
}
},
Gender = PersonGender.Male,
Concurrency = 635519729375200400
};
context.AddObject("People", lewis);
context.SaveChanges();var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var person = await client
.For<People>()
.Set(new People()
{
UserName = "lewisblack",
FirstName = "Lewis",
LastName = "Black",
Emails = new [] { "lewisblack@example.com" },
AddressInfo = new []
{
new Location()
{
Address = "187 Suffolk Ln.",
City = new City
{
CountryRegion = "United States",
Name = "Boise",
Region = "ID"
}
}
},
Gender = PersonGender.Male,
Concurrency = 635519729375200400
})
.InsertEntryAsync();var x = ODataDynamic.Expression;
var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var person = await client
.For(x.People)
.Set(new
{
UserName = "lewisblack",
FirstName = "Lewis",
LastName = "Black",
Emails = new[] { "lewisblack@example.com" },
AddressInfo = new[]
{
new
{
Address = "187 Suffolk Ln.",
City = new
{
CountryRegion = "United States",
Name = "Boise",
Region = "ID"
}
}
},
Gender = PersonGender.Male,
Concurrency = 635519729375200400
})
.InsertEntryAsync();var serviceRoot = 'http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/';
var headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
var newPerson = {
UserName:'lewisblack',
FirstName:'Lewis',
LastName:'Black',
Emails:[
'lewisblack@example.com'
],
AddressInfo:[
{
Address: '187 Suffolk Ln.',
City: {
CountryRegion: 'United States',
Name: 'Boise',
Region: 'ID'
}
}
],
Gender: 'Male'
};
var request = {
requestUri: serviceRoot + 'People',
method: 'POST',
headers: headers,
data: newPerson
};
odatajs.oData.request(
request,
function (data, response) {
var createeRes = response;
},
function (err) {
alert('Fail: ' + err.Message);
}
);auto service_context = std::make_shared<DefaultContainer>(U("http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
auto lewis = std::make_shared<Person>(service_context);
lewis->set_username(U("lewisblack"));
lewis->set_firstname(U("Lewis"));
lewis->set_lastname(U("Black"));
lewis->set_emails({ U("lewisblack@example.com") });
auto location = std::make_shared<Location>(service_context);
location->set_address(U("187 Suffolk Ln."));
auto city = std::make_shared<City>(service_context);
city->set_countryregion(U("United States"));
city->set_name(U("Boise"));
city->set_region(U("ID"));
location->set_city(city);
lewis->set_addressinfo({ location });
lewis->set_gender(PersonGender::Male);
lewis->set_concurrency(635519729375200400);
service_context->add_object(U("People"), lewis).get();var http = require('http');
var serviceHost = 'services.odata.org';
var servicePath = '/v4/TripPinServiceRW/';
var newPerson = {
UserName:'lewisblack',
FirstName:'Lewis',
LastName:'Black',
Emails:[
'lewisblack@example.com'
],
AddressInfo:[
{
Address: '187 Suffolk Ln.',
City: {
CountryRegion: 'United States',
Name: 'Boise',
Region: 'ID'
}
}
],
Gender: 'Male'
};
var postData = JSON.stringify(newPerson);
var options = {
hostname: serviceHost,
path: servicePath + 'People',
port: 80,
method: 'POST',
headers: {
'OData-Version': '4.0',
'OData-MaxVersion': '4.0',
'Content-Type': 'application/json',
'Content-Length': postData.length
}
};
var req = http.request(options, function(res) {
var body = '';
res.on('data', function (chunk) {
body += chunk;
});
res.on('end', function () {
console.log(body);
});
});
req.on('error', function(e) {
console.log('ERROR: ' + e.message);
});
req.write(postData);
req.end();Want to contribute code snippet for another platform or suggest changes to this content? You can edit and submit changes to "Understanding OData in 6 steps" on its Github repository.
In RESTful APIs, resources are usually dependent with each other. For that, the concept of relationships in OData can be defined among resources to add flexibility and richness to the data model. For example, in the TripPin OData service, people are related to the trips that they've booked using the system. Knowing that, Russell would like to invite Lewis to his existing trip in the U.S. by relating that trip to Lewis.
POST http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/People('lewisblack')/Trips/$ref HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Length: 123
Content-Type: application/json
{
"@odata.id":"http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/People('russellwhyte')/Trips(0)"
}var context = new DefaultContainer(new Uri("http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
var trip = context.People.ByKey(userName: "russellwhyte").Trips.ByKey(tripId: 0).GetValue();
var dsc = new DataServiceCollection<Person>(context)
{
context.People.ByKey(userName: "lewisblack").GetValue()
};
dsc[0].Trips.Add(trip);</p>
<p>context.SaveChanges();var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var trip = await client
.For<People>()
.Key("russellwhyte")
.NavigateTo<Trip>()
.Key(0)
.FindEntryAsync();
await client
.For<People>()
.Key("scottketchum")
.LinkEntryAsync(trip);
var person = await client
.For<People>()
.Key("scottketchum")
.Expand(x => x.Trips)
.FindEntryAsync();var x = ODataDynamic.Expression;
var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var trip = await client
.For(x.People)
.Key("russellwhyte")
.NavigateTo(x.Trips)
.Key(0)
.FindEntryAsync();
await client
.For(x.People)
.Key("scottketchum")
.LinkEntryAsync(x.Trips, trip);
var person = await client
.For(x.People)
.Key("scottketchum")
.Expand(x.Trips)
.FindEntryAsync();var serviceRoot = 'http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/';
var headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
var relateBody = {
'@odata.id':'http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/People("russellwhyte")/Trips(0)'
};
var request = {
requestUri: serviceRoot + 'People("lewisblack")/Trips/$ref',
method: 'POST',
headers: headers,
data: relateBody
};
odatajs.oData.request(
request,
function (data, response) {
var res = response;
},
function (err) {
alert('Fail: ' + err.Message);
}
);auto service_context = std::make_shared<DefaultContainer>(U("http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
auto lewis = service_context->create_people_query()->key(U("lewisblack"))->execute_query().get()[0];
auto russell = service_context->create_people_query()->key(U("russellwhyte"))->expand(U("Trips"))->execute_query().get()[0];
auto trip = russell->get_trips()[0];
service_context->add_reference(lewis, U("Trips"), trip).get();var http = require('http');
var serviceHost = 'services.odata.org';
var servicePath = '/v4/TripPinServiceRW/';
var postData = JSON.stringify({
'@odata.id': 'http://services.odata.org/V4/TripPinServiceRW/People("russellwhyte")/Trips(0)'
});
var options = {
hostname: serviceHost,
path: servicePath + 'People("lewisblack")/Trips/$ref',
port: 80,
method: 'POST',
headers: {
'OData-Version': '4.0',
'OData-MaxVersion': '4.0',
'Content-Type': 'application/json',
'Content-Length': postData.length
}
};
var req = http.request(options, function(res) {
var body = '';
res.on('data', function (chunk) {
body += chunk;
});
res.on('end', function () {
console.log(body);
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.write(postData);
req.end();Want to contribute code snippet for another platform or suggest changes to this content? You can edit and submit changes to "Understanding OData in 6 steps" on its Github repository.
In RESTful APIs, there can be some custom operations that contain complicated logic and can be frequently used. For that purpose, OData supports defining functions and actions to represent such operations. They are also resources themselves and can be bound to existing resources. After having explored the TripPin OData service, Russell finds out that it has a function called GetInvolvedPeople from which he can find out the involved people of a specific trip. He invokes the function to find out who else other than him and Lewis goes to that trip in the U.S.
GET http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/People('russellwhyte')/Trips(0)/Microsoft.OData.SampleService.Models.TripPin.GetInvolvedPeople()
HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0var context = new DefaultContainer(new
Uri("http://services.odata.org/v4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
var trip = context.People.ByKey(userName: "russellwhyte").Trips.ByKey(tripId: 0).GetValue();
var people = trip.GetInvolvedPeople().Execute();var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var people = await client
.For<People>()
.Key("scottketchum")
.NavigateTo<Trip>()
.Key(0)
.Function("GetInvolvedPeople")
.ExecuteAsEnumerableAsync();var x = ODataDynamic.Expression;
var client = new ODataClient("http://services.odata.org/v4/TripPinServiceRW/");
var people = await client
.For(x.People)
.Key("scottketchum")
.NavigateTo(x.Trips)
.Key(0)
.Function("GetInvolvedPeople")
.ExecuteAsEnumerableAsync() as IEnumerable<dynamic>;var serviceRoot = 'http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/';
var headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
var request = {
requestUri: serviceRoot + 'People("russellwhyte")/Trips(0)/Microsoft.OData.SampleService.Models.TripPin.GetInvolvedPeople()',
method: 'GET',
headers: headers,
data: null
};
odatajs.oData.request(
request,
function (data, response) {
var involvedPeople = data.value;
},
function (err) {
alert('Fail: ' + err.Message);
}
);auto service_context = std::make_shared<DefaultContainer>(U("http://services.odata.org/V4/(S(34wtn2c0hkuk5ekg0pjr513b))/TripPinServiceRW/"));
auto russell = service_context->create_people_query()->key(U("russellwhyte"))->expand(U("Trips"))->execute_query().get()[0];
auto trip = russell->get_trips()[0];
auto people = trip->GetInvolvedPeople().get();var http = require('http');
var serviceRoot = 'http://services.odata.org/v4/TripPinServiceRW/';
getURL(serviceRoot + 'People("russellwhyte")/Trips(0)/Microsoft.OData.SampleService.Models.TripPin.GetInvolvedPeople()');
function getURL(url) {
var body = '';
http.get(url, function (response) {
response.on('data', function (chunk) {
body+=chunk;
});
response.on('end', function () {
console.log(body);
});
}).on('error', function(e) {
console.log('ERROR: ' + e.message);
});
}Want to contribute code snippet for another platform or suggest changes to this content? You can edit and submit changes to "Understanding OData in 6 steps" on its Github repository.
Participate or follow the standardization technical committee progress at OASIS.
ParticipateShare your ideas, tools and any other information related to OData on OData.org.
ContributeJoin the new mailing list for OData discussion. You can ask or answer questions about the OData protocol.
Join