SAP S/4HANA OData API Capabilities

Type: ERP Integration System: SAP S/4HANA (2408) Confidence: 0.87 Sources: 7 Verified: 2026-03-01 Freshness: 2026-03-01

TL;DR

System Profile

SAP S/4HANA is SAP's flagship ERP system, available in three deployment models: Cloud Public Edition (multi-tenant SaaS), Cloud Private Edition (single-tenant managed), and On-Premise (customer-managed). The OData API surface is the primary programmatic interface, built on the SAP Gateway framework (V2) and the ABAP RESTful Application Programming Model (RAP, V4). This card covers OData-specific capabilities across all deployment models. It does NOT cover SOAP, RFC/BAPI, IDoc, or file-based interfaces, which use fundamentally different protocols and have separate rate limits. [src1, src5]

PropertyValue
VendorSAP
SystemSAP S/4HANA 2408 (On-Premise) / 2502 (Cloud)
API SurfaceOData V2 (SAP Gateway) + OData V4 (RAP)
Current Release2408 (On-Premise), continuously updated (Cloud)
Editions CoveredCloud Public, Cloud Private, On-Premise
DeploymentCloud / On-Premise / Hybrid
API DocsSAP Business Accelerator Hub
StatusGA (V4 strategic direction; V2 maintenance mode)

API Surfaces & Capabilities

SAP S/4HANA organizes APIs into five categories: Application-to-Application (A2A), Business-to-Business (B2B), Application-to-External (A2X), Custom Extension APIs, and Pre-built Business Process APIs. The OData surface handles the vast majority of integration scenarios. [src5]

API SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
OData V2 (Gateway)HTTPS/JSON or Atom/XMLLegacy integrations, existing Fiori apps~1,000/pageICM-configured / ~100 rps (cloud)YesVia $batch
OData V4 (RAP)HTTPS/JSONNew development, Fiori elements~1,000/pageICM-configured / ~100 rps (cloud)YesVia $batch
SOAPHTTPS/XMLLegacy middleware (PI/PO), WS-*Varies by serviceShared with OData (ICM)YesNo
RFC/BAPISAP proprietarySAP-to-SAP, ABAP-nativeNo hard per-call limitWork process poolYesNo
IDocSAP proprietary/EDIEDI, async document exchange1 document/IDoctRFC queueNo (async)Yes

OData V2 vs V4 Feature Comparison

FeatureOData V2OData V4Notes
Default FormatAtom/XML (JSON optional)JSON (default)V4 JSON is lighter, faster to parse
$filterRoot entity onlyRoot + expanded entitiesV4: $expand=Items($filter=Amount gt 100)
$selectRoot entity onlyRoot + expanded entitiesV4: $expand=Items($select=ItemNo,Amount)
$orderbyRoot entity onlyRoot + expanded entitiesV4 enables sorted expansion
$count$inlinecount=allpages$count=true or /$countDifferent syntax, same purpose
$searchNot supportedSupported (free-text)Depends on CDS annotation @Search
Batch ($batch)Multipart/mixedJSON-based batch (preferred)V4 also supports multipart/mixed
Deep InsertCREATE_DEEP_ENTITYNative via navigation propertiesV4 approach is cleaner, atomic
Deep UpdateNot supportedLimited (OData 4.01 draft)RAP support still maturing
Delta QueriesNot standard$deltatoken / delta linksTrack changes since last query
Lambda OperatorsNot supportedany() / all()Filter collections
Update MethodPOST + X-HTTP-METHOD:MERGEPATCH (partial) or PUT (full)V4 follows standard HTTP semantics
Enum TypesNot supportedSupportedStrongly typed enumerations
Actions/FunctionsFunction Imports onlyActions + Functions (separate)V4 has clearer separation

[src3, src4, src7]

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Max records per page~1,000 (default, configurable)All OData queriesServer-driven paging via __next (V2) or @odata.nextLink (V4)
Max $top valueSystem-dependentAll queriesOn-premise: configurable in Gateway; Cloud: enforced server-side
Max $expand depth3-4 levels (typical)All queriesDeep expansion degrades performance exponentially
Max request body size~50 MB (ICM default)$batch, POST, PATCHOn-premise: configurable via ICM; Cloud: fixed
Max response size~50 MB (ICM default)All responsesOn-premise: configurable; Cloud: fixed
Request timeout600s (Cloud) / configurable (On-Premise)All requestsOn-premise: ICM PROCTIMEOUT parameter
CSRF token validitySession-scopedAll write operationsFetch via GET with X-CSRF-Token: Fetch

[src1, src5, src6]

Rolling / Daily Limits

Limit TypeValueWindowEdition Differences
Request throughput~100 rps per API endpointPer secondCloud: ~100 rps baseline, 200 rps burst; On-Premise: ICM thread pool
Daily API call quotaNo hard daily limit (Cloud: fair use)24hCloud: throttled under fair-use policy; On-Premise: resource-bound
Concurrent HTTP connections500 (ICM default)Per systemOn-premise: icm/max_conn; Cloud: managed
Max ICM threads50 (default)Per systemOn-premise: icm/max_threads; Cloud: auto-scaled

[src5]

ICM / Gateway Processing Limits (On-Premise)

Limit TypePer-Transaction ValueNotes
ICM connection timeout60,000 ms (default)icm/conn_timeout
ICM keep-alive timeout300s (default)icm/keep_alive_timeout
Dialog work process time600s (default)rdisp/max_wprun_time
Max sockets2,048 (default)icm/max_sockets
Memory pipe size80 MB (default)mpi/total_size_MB

Authentication

FlowUse WhenToken LifetimeRefresh?Notes
OAuth 2.0 (Client Credentials)S/4HANA Cloud server-to-server~12h (configurable)YesRecommended for Cloud; uses SAP BTP XSUAA
OAuth 2.0 (SAML Bearer)User-context propagation (Cloud)Session-scopedN/APrincipal propagation from IdP
SAML 2.0SSO, enterprise IdP integrationSession-scopedN/AUsed with SAP BTP destinations
Basic AuthenticationOn-premise dev/test onlySession timeout (1800s)N/ADO NOT use in production Cloud
X.509 Client CertificateHigh-security on-premise/CloudCertificate validityN/ARequires PKI infrastructure

[src5]

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START — User needs to integrate with SAP S/4HANA via OData
|-- Which OData version?
|   |-- New project (greenfield)? -> OData V4 (RAP-based)
|   +-- Existing project? -> Check if V4 equivalent exists on api.sap.com
|-- What's the integration pattern?
|   |-- Real-time (individual records, <1s)
|   |   |-- Single entity CRUD? -> Direct GET/POST/PATCH/DELETE
|   |   |-- Multiple related entities?
|   |   |   |-- CREATE? -> Deep Insert (V4 preferred)
|   |   |   +-- UPDATE? -> $batch with changeset
|   |   +-- Need notifications? -> SAP Event Mesh (Cloud) / polling (On-Premise)
|   |-- Batch/Bulk (scheduled, high volume)
|   |   |-- < 1,000 records? -> Simple loop with individual calls
|   |   |-- 1,000-50,000 records? -> $batch (100-500 ops per batch)
|   |   +-- > 50,000 records? -> IDoc or file-based (not OData)
|   |-- Event-driven -> Business Events (Cloud) / polling with $filter (On-Premise)
|   +-- File-based -> Use IDoc/BAPI, not OData
|-- Which direction?
|   |-- Inbound -> Verify CSRF token + ETag handling
|   |-- Outbound -> Use $select to minimize payload
|   +-- Bidirectional -> Conflict resolution via ETags + LastChangedAt
+-- Deployment model?
    |-- Cloud -> Communication Arrangement + OAuth 2.0 (XSUAA)
    |-- Cloud Private -> Similar to Cloud; custom namespaces allowed
    +-- On-Premise -> Basic Auth or X.509; configure ICM for throughput

Quick Reference

Common OData Operations

OperationV2 MethodV4 MethodNotes
List entitiesGET /{EntitySet}GET /{EntitySet}Add $top, $skip, $filter
Get by keyGET /{EntitySet}('{key}')GET /{EntitySet}('{key}')Single quotes around string keys
CreatePOST /{EntitySet}POST /{EntitySet}CSRF token required
Update (partial)POST + X-HTTP-METHOD: MERGEPATCHV2 uses MERGE; V4 uses PATCH
Update (full)PUTPUTReplaces entire entity
DeleteDELETEDELETEETag in If-Match header
Deep insertPOST with nested JSONPOST with navigationAtomic: all or nothing
BatchPOST /$batch (multipart)POST /$batch (JSON)Group related ops in changesets
CountGET /$countGET /$countReturns plain integer
MetadataGET /$metadataGET /$metadataFull EDMX/CSDL schema

Pagination Quick Reference

MechanismOData V2OData V4When to Use
Client-driven$top=100&$skip=200$top=100&$skip=200Small datasets, known total count
Server-driven__next property@odata.nextLinkLarge datasets (mandatory)
$skiptokenIn __next URLIn nextLink URLEfficient server-side cursor
Inline count$inlinecount=allpages$count=trueGet total record count

[src6]

Step-by-Step Integration Guide

1. Discover and Select the API

Browse the SAP Business Accelerator Hub at api.sap.com to find the specific OData service for your business object. [src1, src2]

# Browse S/4HANA Cloud V4 APIs: https://api.sap.com/products/SAPS4HANACloud/apis/ODATAV4
# Browse S/4HANA On-Premise V2 APIs: https://api.sap.com/package/S4HANAOPAPI/odata
# Example: Business Partner API — Service: API_BUSINESS_PARTNER

Verify: Search for your business object and confirm API status is "Active" (not Deprecated).

2. Authenticate and Obtain Access Token

For S/4HANA Cloud, configure a Communication Arrangement and use OAuth 2.0. For on-premise, use Basic Auth or X.509 certificates. [src5]

# S/4HANA Cloud — OAuth 2.0 Client Credentials Flow
curl -X POST "https://{subdomain}.authentication.{region}.hana.ondemand.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id={id}&client_secret={secret}"

# On-Premise — Basic Auth
curl -X GET "https://{host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=10" \
  -u "{user}:{pass}" -H "Accept: application/json"

Verify: HTTP 200 with d.results (V2) or value (V4) array.

3. Fetch CSRF Token for Write Operations

All OData write operations require a CSRF token. Fetch it before any modification request. [src1]

curl -X GET "https://{host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=1" \
  -H "Authorization: Bearer {token}" \
  -H "X-CSRF-Token: Fetch" -H "Accept: application/json" -v 2>&1 | grep csrf

Verify: Response headers contain x-csrf-token: {non-empty-value}.

4. Handle Pagination

Follow server-driven pagination by iterating through nextLink URLs until no more pages exist. [src6]

def fetch_all_pages(base_url, headers, params=None):
    all_results = []
    url = base_url
    while url:
        response = requests.get(url, headers=headers, params=params)
        data = response.json()
        if 'value' in data:  # V4
            all_results.extend(data['value'])
            url = data.get('@odata.nextLink')
        elif 'd' in data:  # V2
            all_results.extend(data['d'].get('results', []))
            url = data['d'].get('__next')
        else:
            break
        params = {}  # nextLink includes all params
    return all_results

Verify: len(all_results) matches the $count value.

Code Examples

Python: Read Business Partners with Pagination

# Input:  SAP S/4HANA OData V2 credentials, filter criteria
# Output: List of business partner records matching criteria
import requests

base_url = "https://{host}/sap/opu/odata/sap/API_BUSINESS_PARTNER"
headers = {"Authorization": "Basic {base64_credentials}", "Accept": "application/json"}
params = {
    "$filter": "CreationDate gt datetime'2025-01-01T00:00:00'",
    "$select": "BusinessPartner,BusinessPartnerFullName,CreationDate",
    "$top": "500", "$inlinecount": "allpages",
}
all_partners = []
response = requests.get(f"{base_url}/A_BusinessPartner", headers=headers, params=params)
data = response.json()
total = int(data['d']['__count'])
all_partners.extend(data['d']['results'])
while '__next' in data['d']:
    response = requests.get(data['d']['__next'], headers=headers)
    data = response.json()
    all_partners.extend(data['d']['results'])
print(f"Fetched {len(all_partners)} of {total} business partners")

JavaScript/Node.js: Deep Insert Sales Order (V4)

// Input:  SAP S/4HANA Cloud OData V4 endpoint, OAuth token
// Output: Created sales order with line items
const { executeHttpRequest } = require("@sap-cloud-sdk/http-client");
const response = await executeHttpRequest(
  { destinationName: "S4HANA_CLOUD" },
  {
    method: "POST",
    url: "/sap/opu/odata4/sap/api_salesorder/srvd_a2x/sap/salesorder/0001/SalesOrder",
    headers: { "Content-Type": "application/json" },
    data: {
      SalesOrderType: "OR", SalesOrganization: "1710",
      DistributionChannel: "10", SoldToParty: "10100001",
      _Item: [
        { Material: "TG11", RequestedQuantity: "10", RequestedQuantityUnit: "PC" },
        { Material: "TG12", RequestedQuantity: "5", RequestedQuantityUnit: "PC" },
      ],
    },
  }
);
console.log(`Created: ${response.data.SalesOrder}`);

cURL: $batch with Multiple Reads (V4 JSON)

# Input:  OAuth access token, S/4HANA Cloud endpoint
# Output: Multiple entity responses in single HTTP round-trip
curl -X POST "https://{tenant}.s4hana.ondemand.com/.../$batch" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"requests":[{"id":"1","method":"GET","url":"A_BusinessPartner?$top=5"},{"id":"2","method":"GET","url":"A_BusinessPartnerAddress?$top=5"}]}'

Data Mapping

OData Property Type Reference

OData V2 TypeOData V4 TypeSAP ABAP TypeNotes
Edm.StringEdm.StringCHAR, NUMCMax length from ABAP data element
Edm.DateTimeEdm.DateTimeOffsetDATS + TIMSV2: /Date(ms)/; V4: ISO 8601
N/AEdm.DateDATSV4 only — date without time
N/AEdm.TimeOfDayTIMSV4 only — time without date
Edm.DecimalEdm.DecimalCURR, QUAN, DECString-encoded to avoid precision loss
Edm.Int32Edm.Int32INT4Standard 32-bit integer
Edm.BooleanEdm.BooleanABAP_BOOLtrue/false
Edm.GuidEdm.GuidSYSUUID_X16UUID format

[src3, src4]

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
400Bad RequestMalformed OData URL, invalid $filter, wrong typesValidate query syntax; check EDMX metadata
401UnauthorizedInvalid/expired credentials or tokenRe-authenticate; verify Communication Arrangement
403ForbiddenMissing CSRF token, insufficient auth, no Comm. ArrangementFetch CSRF; check roles; verify arrangement
404Not FoundWrong service URL, inactive serviceActivate in /IWFND/MAINT_SERVICE (On-Premise)
412Precondition FailedETag mismatchRe-read entity, retry with current ETag
428Precondition RequiredMissing If-Match headerInclude If-Match: {etag} header
429Too Many RequestsRate limit exceeded (Cloud)Exponential backoff; check Retry-After
500Internal Server ErrorABAP runtime error, data inconsistencyCheck /IWFND/ERROR_LOG; review ABAP dump (ST22)

[src1, src5]

Failure Points in Production

Anti-Patterns

Wrong: Using $skip for Large Dataset Extraction

# BAD — $skip forces database to scan and discard rows
for offset in range(0, total_count, page_size):
    response = requests.get(f"{base_url}/A_SalesOrder?$skip={offset}&$top={page_size}")
    # Page 1000+ takes exponentially longer

Correct: Follow Server-Driven Pagination

# GOOD — $skiptoken uses efficient cursor-based pagination
url = f"{base_url}/A_SalesOrder?$top={page_size}"
while url:
    data = requests.get(url, headers=headers).json()
    process_page(data['d']['results'])
    url = data['d'].get('__next')  # Contains $skiptoken

Wrong: Individual API Calls in Loop for Bulk

# BAD — N separate HTTP round-trips
for partner in partners:
    requests.patch(f"{base_url}/A_BusinessPartner('{partner['id']}')", json=partner['data'])

Correct: Use $batch with Changesets

# GOOD — group updates into $batch (100-500 per batch)
for i in range(0, len(partners), 200):
    batch = partners[i:i + 200]
    requests.post(f"{base_url}/$batch", data=build_batch(batch))
    # 10,000 partners = 50 HTTP round-trips

Wrong: Expanding All Navigation Properties

# BAD — massive response sizes, potential timeout
requests.get(f"{base_url}/A_SalesOrder('1000000')?$expand=to_Item,to_Item/to_PricingElement,to_Partner")

Correct: Selective $expand with $select

# GOOD — V4: filter and select on expanded entities
requests.get(f"{base_url_v4}/SalesOrder('1000000')?$expand=_Item($select=SalesOrderItem,Material)&$select=SalesOrder,SoldToParty")

Common Pitfalls

Diagnostic Commands

# Check if OData service is active (On-Premise)
curl -s "https://{host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/$metadata" \
  -u "{user}:{pass}" -H "Accept: application/xml" | head -5
# Expected: <?xml ...><edmx:Edmx ...>

# Test CSRF token fetch
curl -v "https://{host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=1" \
  -u "{user}:{pass}" -H "X-CSRF-Token: Fetch" -H "Accept: application/json" 2>&1 | grep csrf
# Expected: x-csrf-token: {token_value}

# Check S/4HANA Cloud API health
curl -s -o /dev/null -w "%{http_code}" \
  "https://{tenant}.s4hana.ondemand.com/.../$metadata" -H "Authorization: Bearer {token}"
# Expected: 200 (healthy), 401 (auth), 403 (arrangement), 503 (maintenance)

# ICM parameters (On-Premise — SAP GUI transaction RZ10/RZ11)
# icm/max_conn, icm/max_threads, icm/conn_timeout, PROCTIMEOUT

Version History & Compatibility

S/4HANA ReleaseDateStatusKey ChangesMigration Notes
2408 (On-Premise)2024-10CurrentNew V4 APIs for manufacturing, warehousingV2 still supported; V4 recommended
2025 (Private Cloud)2025-02CurrentService Order V2 deprecated; V4 successorMigrate Service Order to V4
2023 (On-Premise)2023-10SupportedRAP-based V4 APIs mainstreamMinimum for broad V4 coverage
1909 (On-Premise)2019-09End of Maintenance (2027)Last pre-RAP releaseV2 only; plan migration to 2023+

[src1, src2]

Deprecation Policy

SAP follows a structured API deprecation lifecycle: APIs are marked "Deprecated" on SAP API Business Hub with a successor identified, then enter a minimum 2-year grace period. Starting 2025, all new APIs are OData V4 exclusively. Check SAP Note 2836302 for the latest deprecation schedule. [src2]

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Real-time CRUD of business objectsBulk migration > 50,000 recordsIDoc flat files or SAP Data Services
Fiori app backend or custom UI5Real-time event streamingSAP Event Mesh + Business Events
Structured queries ($filter, $expand)Unstructured full-text searchSAP Enterprise Search / HANA FTS
Standard objects with published APIsCustom Z-tables without CDS viewsBuild RAP service first
Middleware integration (MuleSoft, Boomi)Large binary file upload/downloadSAP DMS / CMS
Cross-system integration (JSON)SAP-to-SAP in same landscapeRFC/BAPI (lower overhead)

Important Caveats

Related Units