SAP OData Services
OData is the protocol at the heart of modern SAP integration and Fiori, a RESTful, resource-oriented standard for querying and updating data over HTTP with JSON. Understanding OData is essential for UIs and cloud integration.
OData is the protocol every Fiori app uses to talk to the SAP backend and the API style S/4HANA and the cloud products expose for integration. Entities map to business objects, CRUD runs over HTTP verbs, and $filter, $select, $expand, $top and $skip fetch exactly what is needed. Services come from CDS views or SEGW; writes need a CSRF token.
- Watch out: Over-fetching instead of using query options.
Why OData matters in SAP
Every Fiori app talks to the backend via OData, and S/4HANA/cloud products expose OData APIs for integration. It combines REST’s simplicity with powerful, standardised query capabilities, making it ideal for both user interfaces and system-to-system data exchange.
Core capabilities
- Entities & entity sets: resources (e.g. SalesOrder) and collections.
- CRUD via HTTP verbs (GET/POST/PUT/DELETE).
- Query options: $filter, $select, $expand, $orderby, $top/$skip.
- Metadata document describing the service.
One capability worth calling out because it changes how a client is written: navigation properties. An entity declares its relationships, so a client can follow from an order to its items or its customer by URL rather than by making a second unrelated call and joining the results itself. That is what makes an OData model browsable rather than a set of endpoints.
Finding the service you need
Before building anything, the first question is whether SAP already publishes an API for it, and there are three places to look.
The Business Accelerator Hub is the public catalogue of released APIs across S/4HANA, the cloud products and the platform. Each entry carries its documentation, its entity model and, usefully, a try-it-out console that calls a sandbox so you can see real responses before writing a line.
The gateway service catalogue on a system shows what is actually registered and activated there, which is a different question. A service can exist in the Hub and be inactive on your system, which is the most common reason an integration fails on day one.
The metadata document of any service describes itself: entities, properties, types and navigation. Appending the metadata path to a service URL is the fastest way to understand an API you have just been handed.
Naming helps too. Released S/4HANA APIs follow a convention starting with API_, and the entity sets inside them are prefixed A_, which distinguishes a supported public interface from an internal service that happens to be reachable.
That distinction matters more than it sounds: a released API carries a compatibility commitment, and an internal one can change in any release without warning.
Building and consuming
In modern SAP you create OData services from CDS views (via annotations or the RAP model), or classically with the Gateway Service Builder (SEGW). Consumers, Fiori apps, mobile, external systems, call them over HTTP, using query options to fetch precisely what they need.
On the consuming side there are details that catch people once. A modifying call needs a CSRF token, fetched with a GET first and sent with the write, which is a security measure and looks like an authentication failure when it is missing. Batch requests bundle several operations into one round trip, which matters over a slow link. And deep insert creates a parent and its children in one call, which is how an order and its items are posted as one transaction rather than as several that might half succeed.
Call a real API before building anything
Half an hour, and it saves days of assuming.
- Find the API for your object on the Business Accelerator Hub and read its entity model.
- Use the try-it-out console against the sandbox. Fetch a few records and see the actual shape.
- Add
$selectand$filterand confirm they narrow the result as you expect. - Now check your own system: is that service registered and activated. Call its metadata document directly.
- Call it from a client with real credentials, first a read.
- For a write, fetch a CSRF token, then post, and confirm the document appears in the application.
Step four is the one people skip, and it is the difference between an integration that works in testing against the sandbox and one that works against your system.
Versions
OData exists in versions (V2 widely used in SAP, V4 increasingly adopted). Know which version a service uses, as query syntax and features differ.
The practical guidance: check which version a service offers before designing against it, because the query syntax and some semantics differ, and a client library built for one is not automatically fine with the other. New development uses V4 where it is available, and a great deal of what is deployed remains V2, so both will be in a landscape for years. See building services in ABAP for the other side.
What a good service looks like
Whether you are building or judging one, a few properties separate a service that lasts from one that becomes a support burden.
It models a business object, not a table. An entity called SalesOrder with items and a customer navigation is usable; one called VBAK is a table exposed over HTTP.
Names are readable and stable. Field names a consumer can understand without the data dictionary, and names that will not change because they were derived from something internal.
It pushes filters down. A service where $filter reaches the database
scales; one that reads everything and filters afterwards works in testing and fails
later.
It pages by default. A collection that returns everything will eventually be asked for a million records by a client that does not know better.
Errors say something. A message naming the field and the problem lets a consumer fix their call; a generic five hundred means they raise a ticket instead.
It is versioned. Because the first breaking change is coming, and having somewhere to put it is cheaper than negotiating with every consumer.
These are the same properties whether the service is built with RAP, generated from CDS, or written by hand, and they are what to check before depending on somebody else's.
The decisions before consuming an API
- Released or internal. Only build against released APIs. An internal service carries no compatibility promise and will change.
- Synchronous or asynchronous. A call that must complete before the caller continues is one design; fire and forget is another. Volume usually decides it.
- Where credentials live. A secure store rather than configuration or code, and a plan for rotation before the first expiry rather than after it.
- How much you fetch. Filters and field selection at the source rather than in the consumer, which is both faster and the difference between working at test volumes and at real ones.
Batch, deep insert and the write patterns
Reading is straightforward. Writing has patterns worth knowing before designing an integration.
A single create posts one entity. Simple, and one round trip each.
Deep insert creates a parent and its children in one call, which is how an order and its items are posted as a single transaction. Without it, an order created and items that fail leaves a half-created document.
Batch bundles several independent operations into one request. It reduces round trips over a slow link, and each operation succeeds or fails on its own unless they are grouped in a change set.
Change sets inside a batch are the transactional unit: everything in one either commits or rolls back together.
The design question is what should be atomic. An order with its items must be; a hundred unrelated customer updates need not, and making them so means one bad record fails ninety-nine good ones.
The practical caution: batch handling is more complex on the consuming side, because the response contains a status per operation rather than one status, and code that checks only the outer response will report success on a batch where half the operations failed.
Common pitfalls
- Over-fetching instead of using query options.
- Confusing OData V2 and V4 syntax/features.
- Not reading the metadata before consuming.
- Using an internal service because it is reachable. It is not released, it carries no compatibility promise, and it will change.
- Forgetting the CSRF token on writes. The failure reads like an authorisation problem.
- No paging on a large collection. See the API reference, Fiori and OData and BAPIs for the alternatives.
- Testing against the sandbox and assuming the real system matches. The shape is the same and the activation, authorisations and data are not.
Where this goes next
Calling an API is straightforward, and choosing released interfaces and handling tokens, paging and errors so an integration survives is the part you do in the course.
The check to run before designing against any service: is it released, and is it activated on your system. The first decides whether it will still work next year, and the second is the most common reason day one fails.