Calling SAP OData services — URL anatomy, query options, JSON output, sap-client, the CSRF token handshake for writes (and how the SAP Cloud SDK does it), ETags, batch, paging, and what each HTTP failure usually means

How a call to an SAP OData API is shaped and why writes fail. The service URL under /sap/opu/odata/sap/<SERVICE>; V2 query options and $format=json; the sap-client tenant parameter. Writes need an X-CSRF-Token fetched first: the SAP Cloud SDK sends HEAD with X-CSRF-Token fetch (with, then without, a trailing slash), keeps the session cookies, and does this for every non-GET by default. Also covers ETags for updates, $batch, paging, and a symptom table for 401, 403, 404, 405 and 412.

markdownreferencev1E6MH-AWJQstage: calltrust: officialservice: connectivityinterface: odatainterface: restSAP OData URL/sap/opu/odata/sapOData V2OData V4$filter$select$expand$top$skip$format=json$metadatasap-clientX-CSRF-Token fetchCSRF token validation failed403 on POST SAPfetchCsrfToken falseset-cookie with CSRFETag If-Match412 precondition failed$batchpaging __next401 SAP Gateway404 service not foundOData error body

Created 23 Oct 2026 · Modified 23 Oct 2026 · 1 version

Calling SAP OData services

URL anatomy

https://<host>[:port]/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=5&$select=BusinessPartner,BusinessPartnerFullName&$format=json
           \ system  / \ Gateway OData root /  \ service   /  \ entity set  /  \ query options                                          /
  • Service and entity names come from the API's page on the Business Accelerator Hub. Fetch …/<SERVICE>/$metadata to see entity sets, keys and fields.
  • V2 query options: $filter, $select, $expand, $orderby, $top, $skip and $inlinecount. $format=json returns JSON with results under d.results. V4 services follow OData V4 (results under value).
  • sap-client: pick the tenant (client) on systems with more than one, as a query parameter or header. A destination's sap-client property makes the SAP Cloud SDK send it.

The hub's reading: the URL root, d.results and the query options are the standard SAP Gateway OData conventions. Family 3 will cite each published API's own specification.

Writes need a CSRF token

SAP Gateway rejects a modifying request (POST, PUT, PATCH, DELETE) that lacks a valid X-CSRF-Token. The handshake, exactly as the SAP Cloud SDK performs it:

HEAD /resource/path/        X-CSRF-Token: fetch   → token returned? use it
HEAD /resource/path         X-CSRF-Token: fetch   → token returned? use it, otherwise send without one
then: POST /resource/path   X-CSRF-Token: <token> + the cookies the HEAD set
  • Keep the session cookies. The token belongs to the session the HEAD started; the SDK reuses its Set-Cookie values. A token sent without its cookies fails.
  • Default behaviour: the SDK fetches a token for every non-GET (fetchCsrfToken: true), and never for GET.
  • Turning it off: set fetchCsrfToken: false only for targets that don't use CSRF, such as many non-SAP APIs.

Updates, batches and paging

  • ETags: entities with an ETag must be updated with If-Match: <etag> from the last read. A missing or stale value is rejected (412).
  • $batch: several operations in one multipart request. Writes inside the batch share one CSRF token.
  • Paging:
  • page with $top and $skip, or follow the server's next link (__next in V2 JSON, @odata.nextLink in V4);
  • never assume one call returns everything.

What failures usually mean

The hub's reading of the usual causes; family 2's error catalogue will add SAP's verbatim messages.

StatusUsual cause → fix
401Wrong user or password, an expired OAuth token, or a communication user not in the arrangement
403 on a write, with a CSRF-related message or headerMissing, expired or session-less CSRF token. Fetch it with X-CSRF-Token: fetch, and send it with the same cookies
403 on a readThe user lacks the role or authorization for that service or object
404 for the whole serviceThe service isn't activated on the system, or the Cloud Connector doesn't expose that path
405The entity set doesn't allow that method. Check the API specification
412ETag mismatch. Re-read the entity and resend If-Match
400 with an OData error bodyBad $filter syntax, or a wrong field name or type. Read error.message.value (V2) or error.message (V4)

Read this document as markdown — the same bytes an agent receives.