---
# === IDENTITY ===
id: business/erp-integration/salesforce-sap-integration/2026
canonical_question: "How do you integrate Salesforce and SAP S/4HANA - customer/order sync, OData vs RFC, middleware?"
aliases:
  - "Salesforce SAP S/4HANA integration guide"
  - "How to sync customers and orders between Salesforce and SAP"
  - "OData vs RFC vs IDoc for Salesforce-SAP integration"
  - "MuleSoft vs SAP CPI for Salesforce-SAP middleware"
entity_type: erp_integration
domain: business > erp-integration > salesforce-sap-integration
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Salesforce"
    vendor: "Salesforce"
    version: "API v62.0 (Spring '26)"
    edition: "Enterprise, Unlimited, Performance"
    deployment: cloud
    api_surface: "REST, SOAP, Bulk API 2.0, Streaming API, Platform Events"
  - name: "SAP S/4HANA"
    vendor: "SAP"
    version: "2408 (on-premise) / Cloud 2502"
    edition: "Cloud Public, Cloud Private, On-Premise"
    deployment: hybrid
    api_surface: "OData V2/V4, SOAP, RFC/BAPI, IDoc, Event Mesh"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.88
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "Salesforce API v60.0 (Spring '24) deprecated legacy SOAP partner endpoints; SAP S/4HANA 2408 introduced new OData V4 Sales Order API"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Salesforce daily API limit: 100,000 + 1,000/user license (Enterprise) or + 5,000/user license (Unlimited) per 24h rolling window"
  - "Salesforce concurrent API requests: 25 max (production); 5 max (developer/trial)"
  - "SAP S/4HANA Cloud OData: default 1,000 records per page; $top/$skip for pagination"
  - "SAP S/4HANA requires CSRF token fetch before every POST/PUT/PATCH/DELETE — token + session cookies must be cached"
  - "SAP OData $batch max 100 changesets per request (S/4HANA Cloud); on-premise varies by web dispatcher config"
  - "Salesforce governor limits per transaction: 100 SOQL queries, 150 DML statements, 100 callouts, 10s CPU time (sync)"
  - "SAP RFC connections require SAP JCo or SAP .NET Connector license — not open source"
  - "IDocs are asynchronous-only; no synchronous request-response pattern available"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User only needs Salesforce API capabilities (no SAP)"
    use_instead: "business/erp-integration/salesforce-rest-api/2026"
  - condition: "User only needs SAP S/4HANA API capabilities (no Salesforce)"
    use_instead: "business/erp-integration/sap-s4hana-odata-api/2026"
  - condition: "User needs Salesforce-to-NetSuite integration"
    use_instead: "business/erp-integration/salesforce-netsuite-integration/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "real-time sync (individual records, <1s latency)"
      - "batch/bulk (scheduled, high volume)"
      - "event-driven (webhook, CDC, platform events)"
      - "file-based (CSV/XML import/export)"
  - key: data_volume
    question: "What's your daily data volume?"
    type: choice
    options:
      - "< 1,000 records/day"
      - "1,000-100,000 records/day"
      - "> 100,000 records/day"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "inbound (writing to SAP from Salesforce)"
      - "outbound (reading from SAP into Salesforce)"
      - "bidirectional sync"
  - key: sap_deployment
    question: "Is your SAP S/4HANA Cloud or On-Premise?"
    type: choice
    options:
      - "S/4HANA Cloud (Public Edition)"
      - "S/4HANA Cloud (Private Edition)"
      - "S/4HANA On-Premise"
  - key: middleware
    question: "Do you have an existing middleware platform?"
    type: choice
    options:
      - "MuleSoft (Salesforce-owned)"
      - "SAP Integration Suite / CPI"
      - "Boomi"
      - "No middleware yet"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/salesforce-sap-integration/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-02)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/salesforce-rest-api/2026"
      label: "Salesforce REST API capabilities and rate limits"
    - id: "business/erp-integration/sap-s4hana-odata-api/2026"
      label: "SAP S/4HANA OData API capabilities and rate limits"
  solves:
    - id: "business/erp-integration/lead-to-cash-salesforce-sap/2026"
      label: "Lead-to-cash integration playbook: Salesforce to SAP"
  alternative_to:
    - id: "business/erp-integration/salesforce-sap-ecc-integration/2026"
      label: "Salesforce + SAP ECC integration (legacy RFC/IDoc approach)"
  often_confused_with:
    - id: "business/erp-integration/salesforce-connect-odata/2026"
      label: "Salesforce Connect (external objects via OData) — read-only, not bidirectional sync"

# === SOURCES ===
sources:
  - id: src1
    title: "SAP API Business Hub — API_SALES_ORDER_SRV and API_BUSINESS_PARTNER"
    author: SAP
    url: https://api.sap.com/api/API_SALES_ORDER_SRV/overview
    type: official_docs
    published: 2025-10-15
    reliability: authoritative
  - id: src2
    title: "Salesforce Developer Limits and Allocations Quick Reference"
    author: Salesforce
    url: https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_api.htm
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src3
    title: "Integration Patterns in SAP — IDoc, RFC, OData, Events"
    author: MetalHatsCats
    url: https://metalhatscats.com/sap/interview-prep/sap-integration-idocs-apis-middleware-interview-prep
    type: technical_blog
    published: 2025-06-12
    reliability: moderate_high
  - id: src4
    title: "Complete Guide to SAP S/4HANA APIs: REST and SOAP Integration"
    author: APIDeck
    url: https://www.apideck.com/blog/guide-to-sap-4-hana-rest-and-soap-api
    type: technical_blog
    published: 2025-08-20
    reliability: moderate_high
  - id: src5
    title: "Best Practices for Integrating Salesforce with SAP S/4HANA"
    author: VC ERP Consulting
    url: https://www.vc-erp.com/sap-s4hana-salesforce-integration-best-practices/
    type: technical_blog
    published: 2025-09-10
    reliability: moderate_high
  - id: src6
    title: "S/4HANA Cloud X-CSRF Token and E-Tag Validation"
    author: SAP Community
    url: https://community.sap.com/t5/technology-blog-posts-by-members/s-4hana-cloud-x-csrf-token-and-e-tag-validation/ba-p/13427700
    type: community_resource
    published: 2024-11-15
    reliability: moderate_high
  - id: src7
    title: "SAP S/4HANA Integration Solutions — MuleSoft"
    author: MuleSoft
    url: https://www.mulesoft.com/integration/sap/s4hana
    type: vendor_release_notes
    published: 2025-06-01
    reliability: high
---

# Salesforce + SAP S/4HANA Integration: Customer/Order Sync, OData vs RFC, Middleware

## TL;DR

- **Bottom line**: Use OData V2/V4 for greenfield Salesforce-to-SAP S/4HANA integrations; RFC/BAPI only for on-premise custom logic not exposed via OData; IDocs only for high-volume batch or legacy EDI. A middleware layer (MuleSoft or SAP CPI) is required for production — point-to-point breaks at scale.
- **Key limit**: Salesforce API calls are capped at 100,000 + 1,000/user license per 24h (Enterprise); SAP OData returns max 1,000 records/page by default. Both sides constrain high-volume real-time sync.
- **Watch out for**: SAP CSRF token handling — every write operation requires a fresh token fetched via GET + session cookies cached. Forgetting this is the #1 cause of "403 Forbidden" errors in new integrations.
- **Best for**: Bidirectional customer master + sales order sync between Salesforce CRM and SAP S/4HANA ERP in organizations running both systems.
- **Authentication**: Salesforce uses OAuth 2.0 (JWT bearer for server-to-server); SAP S/4HANA Cloud uses OAuth 2.0 client credentials with CSRF token overlay; on-premise uses Basic Auth + CSRF or X.509 certificates.

## System Profile

This card covers the integration between Salesforce (CRM, source of truth for leads, opportunities, and customer-facing data) and SAP S/4HANA (ERP, source of truth for financial master data, pricing, inventory, and order fulfillment). It applies to SAP S/4HANA Cloud (Public and Private Edition) and On-Premise 2021+ releases. For legacy SAP ECC (pre-S/4HANA), RFC/IDoc patterns differ significantly — see the SAP ECC integration card instead.

The typical data flow is: Salesforce opportunity closed-won triggers order creation in SAP S/4HANA; customer master data syncs bidirectionally; pricing and inventory reads flow from SAP to Salesforce. A middleware orchestrator handles mapping, error recovery, and monitoring.

| System | Role | API Surface | Direction |
|---|---|---|---|
| Salesforce (API v62.0) | CRM — source of truth for customers, leads, opportunities | REST API, Bulk API 2.0, Platform Events | Outbound (customer/order push) + Inbound (pricing/inventory pull) |
| SAP S/4HANA (2408 / Cloud 2502) | ERP — financial master, inventory, fulfillment | OData V2/V4, SOAP, RFC/BAPI, IDoc | Inbound (order creation) + Outbound (customer/pricing/stock) |
| Middleware (MuleSoft / SAP CPI) | Integration orchestrator | Connectors for both systems | Bidirectional routing, transformation, error handling |

## API Surfaces & Capabilities

### Salesforce Side

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| REST API | HTTPS/JSON | Individual record CRUD, <2K records | 2,000 per SOQL query | 100K+ calls/24h (edition-dependent) | Yes | No |
| Bulk API 2.0 | HTTPS/CSV | ETL, data migration, >2K records | 150M per file | 15,000 batches/24h | No | Yes |
| SOAP API | HTTPS/XML | Metadata operations, legacy integrations | 2,000 records | Shared with REST limit | Yes | No |
| Composite API | HTTPS/JSON | Multi-object operations in one call | 25 subrequests | Counts as 1 API call | Yes | No |
| Platform Events | CometD/gRPC | Real-time event notifications | N/A | Edition-dependent (200K-1M events/24h) | Yes | N/A |
| Change Data Capture | CometD | Track field-level changes | N/A | 24h replay window | Yes | N/A |

[src2]

### SAP S/4HANA Side

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| OData V2 | HTTPS/JSON or XML | Standard CRUD (most existing services) | 1,000/page (configurable via $top) | ~100 req/s default (Cloud) | Yes | Via $batch |
| OData V4 | HTTPS/JSON | Newer APIs (recommended for greenfield) | 1,000/page (configurable) | ~100 req/s default (Cloud) | Yes | Via $batch |
| SOAP (WSDL) | HTTPS/XML | Legacy, metadata, WS-Security scenarios | Varies by service | Fair-use / throttled | Yes | No |
| RFC/BAPI | SAP protocol (JCo/.NET Connector) | Custom ABAP logic not exposed via OData | Single call | Connection pool dependent | Yes | No |
| IDoc | ALE/EDI | Bulk document transfer, EDI, legacy | Unlimited (async queue) | Throughput-dependent | No (async) | Yes |
| SAP Event Mesh | AMQP/MQTT/REST | Real-time pub/sub, multi-consumer | N/A | Subscription-tier dependent | Yes | N/A |

[src1, src3, src4]

## Rate Limits & Quotas

### Salesforce Per-Request Limits

| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per SOQL query | 2,000 | REST/SOAP API | Use queryMore/nextRecordsUrl for pagination |
| Max request body size | 50 MB | REST API | |
| Max composite subrequests | 25 | Composite API | All-or-nothing by default |
| Max batch file size (Bulk API 2.0) | 150 MB | Bulk API 2.0 | Split larger files |
| Max records per batch | 10,000 | Bulk API 2.0 | |
| API request timeout | 600,000 ms (10 min) | All REST/SOAP | |

[src2]

### Salesforce Rolling / Daily Limits

| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| Total API calls | 100,000 base | 24h rolling | Enterprise: +1,000/license; Unlimited: +5,000/license; Developer: 15,000 total |
| Concurrent API requests | 25 | Per org | Developer/Trial: 5 |
| Bulk API batches | 15,000 | 24h rolling | Shared across editions |
| Streaming API events | 200,000-1,000,000 | 24h | Enterprise: 200K; Unlimited/Performance: 1M |
| Streaming API topics | 50-100 | Per org | Enterprise: 50; Unlimited: 100 |

[src2]

### Salesforce Governor Limits (Per Transaction)

| Limit Type | Per-Transaction Value | Notes |
|---|---|---|
| SOQL queries | 100 | Includes queries from triggers — cascading triggers consume from same pool |
| DML statements | 150 | Each insert/update/delete counts as 1, regardless of record count |
| Callouts (HTTP requests) | 100 | External HTTP requests within a single transaction |
| CPU time | 10,000 ms (sync) / 60,000 ms (async) | Exceeded = transaction abort |
| Heap size | 6 MB (sync) / 12 MB (async) | |
| Total describe calls | 100 | |

[src2]

### SAP S/4HANA Rate Limits

| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Default throughput | ~100 requests/second | OData (Cloud) | Managed via SAP API Management; on-premise via web dispatcher |
| Burst capacity | ~200 requests/second | OData (Cloud) | Short-burst tolerance before throttling |
| Max page size | 1,000 records/page | OData queries | Configurable via $top; use $skip for pagination or server-driven paging |
| $batch max changesets | 100 per request | OData $batch (Cloud) | On-premise varies by web dispatcher configuration |
| Daily quota | Varies by subscription tier | S/4HANA Cloud | Not publicly documented — contact SAP support for specifics |
| HTTP 429 response | Retry-After header included | All Cloud APIs | Exponential backoff recommended |

[src1, src4]

## Authentication

### Salesforce Authentication

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 JWT Bearer | Server-to-server integration (recommended) | Session timeout (default 2h) | New JWT per request | Requires connected app with digital certificate |
| OAuth 2.0 Web Server Flow | User-context operations | Access: 2h; Refresh: until revoked | Yes | Requires callback URL; supports MFA |
| OAuth 2.0 Client Credentials | Server-to-server (simpler, newer) | 2h | No (new token per request) | Spring '23+; no user context |
| Username-Password (legacy) | Testing only | Session timeout | No | Do NOT use in production — no MFA support |

[src2]

### SAP S/4HANA Authentication

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 Client Credentials | S/4HANA Cloud integrations | Configurable (default 12h) | Yes | Requires Communication Arrangement in S/4HANA Cloud |
| Basic Auth + CSRF | On-premise, simple setups | Session-based | Fetch new CSRF per session | CSRF token required for all write operations |
| X.509 Client Certificates (mTLS) | High-security environments | Certificate validity period | N/A | Recommended for production on-premise |
| SAML 2.0 / Principal Propagation | SSO scenarios, user-context | Assertion lifetime | N/A | Via SAP Cloud Connector for on-premise |

[src4, src6]

### Authentication Gotchas

- **SAP CSRF token is session-bound**: Fetch via `GET` with header `X-CSRF-Token: Fetch`, then cache the returned token AND all `Set-Cookie` values. Send both on every subsequent write request. If you forget the cookies, the token is invalid. [src6]
- **CSRF token expires on document update**: After updating a record, the ETag resets — you must re-fetch the CSRF token before further writes to the same entity. [src6]
- **Salesforce JWT flow requires a connected app with digital certificate**: Self-signed works for dev; CA-signed required for production. The integration user's profile determines all API permissions. [src2]
- **SAP Cloud Communication Arrangements**: In S/4HANA Cloud, you must create a Communication Arrangement linking a Communication Scenario (e.g., SAP_COM_0109 for Sales Order) to a Communication System (your middleware). This is not optional — direct API calls without it return 401. [src1]
- **Salesforce sandbox tokens are org-specific**: A token from production will not work in a sandbox, and vice versa. Each sandbox has its own OAuth endpoints. [src2]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- **Salesforce API call ceiling**: Enterprise Edition is limited to 100,000 + 1,000/user license API calls per 24h. High-frequency polling patterns can exhaust this within hours. Use event-driven (Platform Events / CDC) for real-time, not polling. [src2]
- **SAP OData is not true REST**: OData V2 uses `$filter`, `$expand`, `$select` with SAP-specific quirks (e.g., `guid'...'` format for UUIDs, datetime'...' for dates). Do not assume standard REST conventions. [src4]
- **SAP RFC requires proprietary connector**: RFC/BAPI calls require SAP Java Connector (JCo) or SAP .NET Connector, both under SAP licensing. Cannot be used from serverless/container platforms without JCo native libraries installed. [src3]
- **IDocs are asynchronous-only**: No request-response pattern. You send an IDoc and poll for status or process the response IDoc when SAP sends it back. Not suitable for real-time validation (use OData or RFC instead). [src3]
- **Salesforce governor limits cascade through triggers**: If your integration writes to an object that has Apex triggers, and those triggers make further SOQL/DML/callout operations, they all count against the same per-transaction limits. This is the #1 cause of unexpected failures in Salesforce integrations. [src2]
- **SAP S/4HANA Cloud restricts custom ABAP**: Public Cloud Edition does not allow custom RFC function modules — only released OData/SOAP APIs or extensibility via key user tools and RAP (ABAP RESTful Application Programming Model). [src1]
- **No direct Salesforce-to-SAP connection in production**: Both vendors recommend a middleware layer. Salesforce callout limits (100 per transaction) and SAP CSRF token requirements make direct integration fragile and hard to monitor. [src5, src7]

## Integration Pattern Decision Tree

```
START — Salesforce ↔ SAP S/4HANA Integration
├── What protocol to use on the SAP side?
│   ├── S/4HANA Cloud (Public Edition)?
│   │   ├── YES → OData V4 (preferred) or OData V2 (more services available)
│   │   │   └── Custom logic needed? → Use RAP custom OData services
│   │   └── NO (Private Edition or On-Premise) ↓
│   ├── Standard business API exists on api.sap.com?
│   │   ├── YES → Use OData (V4 if available, V2 fallback)
│   │   └── NO → RFC/BAPI via middleware (requires JCo connector)
│   └── High-volume batch transfer?
│       ├── YES, >10K records → IDoc (via ALE or middleware IDoc adapter)
│       └── NO → OData $batch (up to 100 changesets per request)
│
├── What protocol to use on the Salesforce side?
│   ├── Real-time individual records (<2K)?
│   │   └── REST API (Composite for multi-object)
│   ├── Batch/bulk (>2K records)?
│   │   └── Bulk API 2.0
│   ├── Need change notifications FROM Salesforce?
│   │   ├── Field-level tracking needed? → Change Data Capture
│   │   └── Custom event structure? → Platform Events
│   └── Need to show SAP data in Salesforce without copying?
│       └── Salesforce Connect (OData) — read-only external objects
│
├── Which middleware?
│   ├── Salesforce is your strategic platform?
│   │   └── MuleSoft (Salesforce-owned, best SF connectors) [src7]
│   ├── SAP is your strategic platform?
│   │   └── SAP Integration Suite / CPI (best SAP connectors, Cloud Connector for on-prem) [src3]
│   ├── Neither dominant / multi-vendor landscape?
│   │   └── Boomi, Workato, or Jitterbit (neutral ground)
│   └── Budget-constrained?
│       └── SAP CPI is included in some S/4HANA Cloud licenses — check entitlement
│
├── Data direction?
│   ├── Salesforce → SAP (order creation, customer sync)
│   │   └── Middleware maps SF objects → SAP OData entities, handles CSRF
│   ├── SAP → Salesforce (pricing, inventory, fulfillment status)
│   │   └── Middleware queries SAP OData → transforms → REST API upsert to SF
│   └── Bidirectional (customer master sync)
│       └── Define source-of-truth per field, implement conflict resolution
│           └── Common: SAP = financial fields, Salesforce = contact/marketing fields
│
└── Error handling strategy?
    ├── Zero-loss required → implement idempotency keys + dead letter queue in middleware
    └── Best-effort → retry 3x with exponential backoff, then log and alert
```

## Quick Reference

### Key OData Services for Salesforce-SAP Integration

| SAP OData Service | API Hub ID | Use Case | OData Version | Key Entity Sets |
|---|---|---|---|---|
| Business Partner | API_BUSINESS_PARTNER | Customer master sync | V2 | A_BusinessPartner, A_BusinessPartnerAddress, A_BuPaAddressUsage |
| Sales Order | API_SALES_ORDER_SRV | Order creation from SF | V2 | A_SalesOrder, A_SalesOrderItem, A_SalesOrderScheduleLine |
| Sales Order (V4) | API_SALESORDER_0001 | Order creation (newer) | V4 | SalesOrder, SalesOrderItem |
| Product Master | API_PRODUCT_SRV | Product catalog sync | V2 | A_Product, A_ProductDescription, A_ProductPlant |
| Material Availability (ATP) | API_MATERIAL_AVAILABILITY_INFO | Stock check from SF | V2 | SupplyDemandItem |
| Pricing Conditions | API_SLSPRICINGCONDITIONRECORD_SRV | Pricing sync | V2 | A_SlsPrcgCndnRecdValidity |
| Credit Management | API_CREDIT_MANAGEMENT_MASTER | Credit check for orders | V2 | CrdtMgmtBusinessPartner |
| Delivery | API_OUTBOUND_DELIVERY_SRV_0002 | Fulfillment status sync | V2 | A_OutbDeliveryHeader, A_OutbDeliveryItem |

[src1]

### Salesforce Objects for SAP Integration

| Salesforce Object | SAP Equivalent | Sync Direction | Typical Frequency |
|---|---|---|---|
| Account | Business Partner (BP) | Bidirectional | Real-time or 15-min batch |
| Contact | Business Partner Contact | Bidirectional | Real-time |
| Opportunity (Closed Won) | Sales Order (VA01) | SF → SAP | Event-driven (on close) |
| Product2 | Material Master (MM01) | SAP → SF | Daily batch |
| PricebookEntry | Condition Records (VK11) | SAP → SF | Daily or on-change |
| Order (SF Order object) | Sales Order (VA01) | SF → SAP or bidirectional | Real-time |
| Custom: Inventory__c | Material Availability (ATP) | SAP → SF | Real-time or 5-min polling |
| Custom: Delivery_Status__c | Outbound Delivery (VL01N) | SAP → SF | Event-driven |

## Step-by-Step Integration Guide

### 1. Set up authentication on both sides

**Salesforce**: Create a Connected App (Setup > App Manager > New Connected App) with OAuth 2.0 JWT Bearer flow. Upload a self-signed certificate (dev) or CA-signed certificate (prod). Note the Consumer Key. [src2]

**SAP S/4HANA Cloud**: Create a Communication Arrangement (e.g., scenario SAP_COM_0008 for Business Partner). This generates a Communication User and API credentials. For on-premise, create an RFC user with the required authorization objects. [src1]

```bash
# Test Salesforce auth — obtain access token via JWT Bearer
curl -X POST https://login.salesforce.com/services/oauth2/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  -d "assertion=${JWT_TOKEN}"
# Expected: {"access_token":"00D...","instance_url":"https://yourorg.my.salesforce.com",...}
```

**Verify**: `curl -H "Authorization: Bearer ${SF_TOKEN}" ${INSTANCE_URL}/services/data/v62.0/limits/` returns your org's API usage and remaining limits.

### 2. Fetch SAP CSRF token and test OData connectivity

Before any write operation, fetch the CSRF token from SAP. [src6]

```bash
# Fetch CSRF token from SAP S/4HANA
curl -X GET "https://{sap-host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=1" \
  -H "Authorization: Basic ${SAP_CREDENTIALS}" \
  -H "X-CSRF-Token: Fetch" \
  -H "Accept: application/json" \
  -c cookies.txt \
  -D response_headers.txt

# Extract token from response headers
CSRF_TOKEN=$(grep -i "x-csrf-token" response_headers.txt | awk '{print $2}' | tr -d '\r')
echo "CSRF Token: ${CSRF_TOKEN}"
```

**Verify**: Response returns HTTP 200 with a `x-csrf-token` header value (not "Required").

### 3. Sync customer/account data (Salesforce Account to SAP Business Partner)

Map Salesforce Account fields to SAP Business Partner and push via OData. [src1, src5]

```bash
# Create Business Partner in SAP from Salesforce Account data
curl -X POST "https://{sap-host}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner" \
  -H "Authorization: Basic ${SAP_CREDENTIALS}" \
  -H "X-CSRF-Token: ${CSRF_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -b cookies.txt \
  -d '{
    "BusinessPartnerCategory": "2",
    "BusinessPartnerGrouping": "0001",
    "OrganizationBPName1": "Acme Corp",
    "SearchTerm1": "ACME",
    "to_BusinessPartnerAddress": [{
      "StreetName": "123 Main St",
      "CityName": "San Francisco",
      "Region": "CA",
      "PostalCode": "94105",
      "Country": "US"
    }]
  }'
# Expected: HTTP 201 with BusinessPartner number in response
```

**Verify**: `GET /A_BusinessPartner('${BP_NUMBER}')` returns the newly created business partner.

### 4. Create Sales Order in SAP from Salesforce Opportunity (Closed Won)

When a Salesforce Opportunity is marked Closed Won, create a corresponding Sales Order in SAP. [src1]

```bash
# Create Sales Order in SAP S/4HANA
curl -X POST "https://{sap-host}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder" \
  -H "Authorization: Basic ${SAP_CREDENTIALS}" \
  -H "X-CSRF-Token: ${CSRF_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -b cookies.txt \
  -d '{
    "SalesOrderType": "OR",
    "SalesOrganization": "1000",
    "DistributionChannel": "10",
    "OrganizationDivision": "00",
    "SoldToParty": "'"${SAP_BP_NUMBER}"'",
    "PurchaseOrderByCustomer": "'"${SF_OPPORTUNITY_ID}"'",
    "to_Item": [{
      "SalesOrderItem": "10",
      "Material": "MAT-001",
      "RequestedQuantity": "5",
      "RequestedQuantityUnit": "EA"
    }]
  }'
# Expected: HTTP 201 with SalesOrder number
```

**Verify**: `GET /A_SalesOrder('${SO_NUMBER}')?$expand=to_Item` returns the order with line items.

### 5. Implement polling/event-driven sync for delivery status (SAP to Salesforce)

Read outbound delivery data from SAP and update a custom object in Salesforce. [src1, src2]

```bash
# Query SAP for deliveries changed since last sync
curl -X GET "https://{sap-host}/sap/opu/odata/sap/API_OUTBOUND_DELIVERY_SRV_0002/A_OutbDeliveryHeader?\
$filter=LastChangeDateTime gt datetime'2026-03-01T00:00:00'&\
$select=DeliveryDocument,SoldToParty,ActualDeliveryRoute,OverallSDProcessStatus&\
$expand=to_DeliveryDocumentItem" \
  -H "Authorization: Basic ${SAP_CREDENTIALS}" \
  -H "Accept: application/json"

# Upsert delivery status to Salesforce
curl -X PATCH "${INSTANCE_URL}/services/data/v62.0/sobjects/Delivery_Status__c/SAP_Delivery_Number__c/${DELIVERY_DOC}" \
  -H "Authorization: Bearer ${SF_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "Status__c": "Shipped",
    "SAP_Delivery_Number__c": "'"${DELIVERY_DOC}"'",
    "Tracking_Number__c": "'"${TRACKING}"'"
  }'
```

**Verify**: Query `SELECT Id, Status__c FROM Delivery_Status__c WHERE SAP_Delivery_Number__c = '${DELIVERY_DOC}'` returns the updated record.

## Code Examples

### Python: Bidirectional Customer Sync via MuleSoft-style Middleware Logic

```python
# Input:  Salesforce Account changes (CDC event) + SAP Business Partner API access
# Output: Synced customer records in both systems with conflict resolution

import requests
from datetime import datetime, timezone

# --- Configuration ---
SF_INSTANCE = "https://yourorg.my.salesforce.com"
SAP_HOST = "https://your-s4hana.sap-cloud.com"
SF_TOKEN = "00D..."  # Obtain via OAuth 2.0 JWT Bearer
SAP_CREDS = ("COMM_USER", "password")  # Communication user

def get_sap_csrf_token(session: requests.Session) -> str:
    """Fetch CSRF token + cookies from SAP. Must be called before any write."""
    resp = session.get(
        f"{SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=1",
        headers={"X-CSRF-Token": "Fetch", "Accept": "application/json"},
        auth=SAP_CREDS
    )
    resp.raise_for_status()
    return resp.headers["x-csrf-token"]

def sync_account_to_bp(account: dict, session: requests.Session, csrf: str) -> str:
    """Create or update SAP Business Partner from Salesforce Account."""
    payload = {
        "BusinessPartnerCategory": "2",  # Organization
        "OrganizationBPName1": account["Name"][:40],  # SAP max 40 chars
        "SearchTerm1": account["Name"][:20].upper(),
        "to_BusinessPartnerAddress": [{
            "StreetName": account.get("BillingStreet", ""),
            "CityName": account.get("BillingCity", ""),
            "Region": account.get("BillingState", ""),
            "PostalCode": account.get("BillingPostalCode", ""),
            "Country": account.get("BillingCountry", "US")
        }]
    }
    resp = session.post(
        f"{SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner",
        json=payload,
        headers={"X-CSRF-Token": csrf, "Content-Type": "application/json"},
        auth=SAP_CREDS
    )
    if resp.status_code == 429:  # Rate limited
        retry_after = int(resp.headers.get("Retry-After", 5))
        raise Exception(f"SAP rate limited. Retry after {retry_after}s")
    resp.raise_for_status()
    return resp.json()["d"]["BusinessPartner"]
```

### JavaScript/Node.js: Sales Order Creation from Salesforce Opportunity

```javascript
// Input:  Salesforce Opportunity ID (Closed Won event)
// Output: SAP Sales Order number

const axios = require('axios'); // v1.7+

async function getSapCsrfToken(sapHost, auth) {
  const resp = await axios.get(
    `${sapHost}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?$top=1`,
    {
      headers: { 'X-CSRF-Token': 'Fetch', 'Accept': 'application/json' },
      auth,
      withCredentials: true
    }
  );
  return {
    token: resp.headers['x-csrf-token'],
    cookies: resp.headers['set-cookie']
  };
}

async function createSalesOrder(sapHost, auth, orderData) {
  const { token, cookies } = await getSapCsrfToken(sapHost, auth);

  const payload = {
    SalesOrderType: 'OR',
    SalesOrganization: orderData.salesOrg,
    DistributionChannel: orderData.distChannel,
    OrganizationDivision: orderData.division,
    SoldToParty: orderData.sapCustomerNumber,
    PurchaseOrderByCustomer: orderData.sfOpportunityId,
    to_Item: orderData.lineItems.map((item, idx) => ({
      SalesOrderItem: String((idx + 1) * 10).padStart(6, '0'),
      Material: item.sapMaterialNumber,
      RequestedQuantity: String(item.quantity),
      RequestedQuantityUnit: item.uom || 'EA'
    }))
  };

  const resp = await axios.post(
    `${sapHost}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder`,
    payload,
    {
      headers: {
        'X-CSRF-Token': token,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Cookie': cookies.join('; ')
      },
      auth
    }
  );

  return resp.data.d.SalesOrder; // e.g., "0000012345"
}
```

### cURL: Quick Test of End-to-End Connectivity

```bash
# Input:  SAP credentials, Salesforce token
# Output: Validates both APIs are reachable and authenticated

# 1. Test Salesforce REST API
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer ${SF_TOKEN}" \
  "${SF_INSTANCE}/services/data/v62.0/"
# Expected: 200

# 2. Test SAP OData API + CSRF fetch
curl -s -o /dev/null -w "%{http_code}" \
  -H "X-CSRF-Token: Fetch" \
  -H "Accept: application/json" \
  -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?\$top=1"
# Expected: 200

# 3. Test Salesforce API usage (remaining limit)
curl -s -H "Authorization: Bearer ${SF_TOKEN}" \
  "${SF_INSTANCE}/services/data/v62.0/limits/" | \
  python3 -c "import sys,json; d=json.load(sys.stdin); print(f\"API calls remaining: {d['DailyApiRequests']['Remaining']}/{d['DailyApiRequests']['Max']}\")"
```

## Data Mapping

### Field Mapping Reference

| Salesforce Field | SAP S/4HANA Field | Type | Transform | Gotcha |
|---|---|---|---|---|
| Account.Name | BP.OrganizationBPName1 | String | Truncate to 40 chars | SAP max 40 chars; SF max 255 chars |
| Account.BillingStreet | BPAddress.StreetName | String | Direct | SAP splits into StreetName + HouseNumber; SF combines |
| Account.BillingCountry | BPAddress.Country | String | ISO 3166-1 alpha-2 | Must be 2-letter code in both systems |
| Account.Phone | BPAddress.PhoneNumber | String | Strip formatting | SAP stores raw digits; remove +, -, (), spaces |
| Account.Website | BP.BusinessPartnerURL | String | Direct | SAP max 132 chars |
| Opportunity.Amount | SalesOrder.TotalNetAmount | Currency | Read-only in SAP (calculated) | Cannot set directly — SAP calculates from items + pricing |
| Opportunity.CloseDate | SalesOrder.RequestedDeliveryDate | Date | SF: YYYY-MM-DD; SAP: /Date(ms)/ or YYYY-MM-DD (V4) | OData V2 uses epoch ms format; V4 uses ISO 8601 |
| OpportunityLineItem.Quantity | SOItem.RequestedQuantity | Decimal | Direct | SAP stores as string in OData response |
| OpportunityLineItem.UnitPrice | SOItem (via pricing) | Currency | Cannot set directly | SAP pricing determined by condition records — not item-level price |
| Product2.ProductCode | Material.Material | String | Pad to 18 chars (leading zeros) | SAP Material IDs are 18-char with leading zeros; SF stores trimmed |

[src1, src5]

### Data Type Gotchas

- **Date formats differ**: Salesforce uses ISO 8601 (`2026-03-02`). SAP OData V2 uses `datetime'2026-03-02T00:00:00'` or `/Date(1709337600000)/` (epoch ms). OData V4 uses ISO 8601. Always check which OData version you are hitting. [src4]
- **Currency handling**: Salesforce stores amounts in the org's default currency as decimals. SAP stores amounts with currency-dependent decimal places (JPY has 0 decimals; USD/EUR have 2). Always transmit the currency code alongside the amount. [src1]
- **SAP Material ID padding**: SAP Materials are 18-character strings with leading zeros (`000000000000MAT001`). Salesforce typically stores the human-readable version (`MAT001`). Your middleware must pad/trim consistently. [src5]
- **Multi-value fields**: Salesforce multi-select picklists serialize as semicolon-delimited in API responses. SAP has no native equivalent — map to multiple rows or a custom table. [src2]
- **Null handling**: SAP OData returns empty strings for null fields, not JSON `null`. Your deserialization must treat `""` as null to avoid overwriting good data with blanks. [src4]

## Error Handling & Failure Points

### Common Error Codes

| Code | System | Meaning | Cause | Resolution |
|---|---|---|---|---|
| 403 Forbidden | SAP | CSRF token invalid/expired | Stale token or missing session cookies | Re-fetch CSRF token with cookies before retry |
| 429 Too Many Requests | Both | Rate limit exceeded | Too many API calls in window | Exponential backoff: wait 2^n seconds, max 5 retries |
| 401 Unauthorized | SAP | Authentication failed | Invalid credentials or missing Communication Arrangement | Verify Communication User + Arrangement in S/4HANA Cloud |
| REQUEST_LIMIT_EXCEEDED | Salesforce | Daily API limit hit | Exceeded 24h rolling API call quota | Check limits via /services/data/v62.0/limits/; purchase add-on or optimize calls |
| UNABLE_TO_LOCK_ROW | Salesforce | Record locked | Concurrent updates to same record | Retry with jitter; serialize updates to same record |
| INVALID_FIELD | Salesforce | Field not writable | Wrong API version or missing field-level security | Check FLS permissions on integration user's profile |
| CX_SXML_PARSE_ERROR | SAP | Malformed JSON/XML payload | Invalid payload structure or encoding | Validate payload against OData metadata ($metadata endpoint) |
| /IWBEP/CX_MGW_BUSI_EXCEPTION | SAP | Business logic rejection | e.g., invalid material, pricing not maintained | Read error message details — SAP returns structured error with message class and number |

[src1, src2, src4, src6]

### Failure Points in Production

- **CSRF token caching gone wrong**: Middleware caches the CSRF token but not the session cookies. Every SAP write fails with 403. Fix: `Cache CSRF token AND all Set-Cookie headers together; invalidate both on 403`. [src6]
- **Salesforce governor limit breach from trigger cascading**: Integration inserts 200 records, each triggers an Apex trigger that makes a SOQL query and a DML statement. That is 200 SOQL (limit 100) — transaction aborts. Fix: `Bulkify all Apex triggers; use collections, not loops. Integration should use Composite/Bulk API to control batch size`. [src2]
- **SAP OData V2 date format silently rejected**: Sending ISO 8601 dates to an OData V2 service results in a cryptic parse error. Fix: `Use datetime'YYYY-MM-DDT00:00:00' format for V2; ISO 8601 for V4 only`. [src4]
- **Material number padding mismatch**: Salesforce stores `MAT001`, middleware sends to SAP without padding, SAP returns "Material not found". Fix: `Always pad Material IDs to 18 chars with leading zeros before sending to SAP`. [src5]
- **SAP batch partial failure unhandled**: OData `$batch` with `continue-on-error` returns 200 overall but individual changesets may have failed. Middleware logs success without checking each changeset response. Fix: `Parse each changeset response individually; log failed changesets to dead letter queue`. [src1]
- **OAuth refresh token expiry in Salesforce**: Refresh tokens expire after 90 days of non-use if "Refresh Token Policy" is set to expire. Unattended integration breaks silently after a holiday period. Fix: `Set refresh token policy to "Refresh token is valid until revoked" in Connected App settings, or use JWT Bearer flow which does not use refresh tokens`. [src2]

## Anti-Patterns

### Wrong: Polling SAP every 60 seconds for changes

```javascript
// BAD — wastes SAP API calls and Salesforce API calls; misses changes between polls
setInterval(async () => {
  const changes = await fetchAllCustomersFromSAP(); // fetches ALL records
  for (const customer of changes) {
    await upsertToSalesforce(customer); // 1 API call per record
  }
}, 60000);
```

### Correct: Use SAP Event Mesh or delta query with LastChangeDateTime

```javascript
// GOOD — only fetch changed records; minimize API calls
async function syncDelta(lastSyncTimestamp) {
  const filter = `LastChangeDateTime gt datetime'${lastSyncTimestamp}'`;
  const changes = await fetchFromSAP(`/A_BusinessPartner?$filter=${filter}`);
  if (changes.length > 0) {
    // Batch upsert to Salesforce using Composite API (25 per call)
    const batches = chunk(changes, 25);
    for (const batch of batches) {
      await compositeUpsert(batch);
    }
  }
  return new Date().toISOString(); // new checkpoint
}
```

### Wrong: Direct Salesforce-to-SAP callouts from Apex triggers

```apex
// BAD — makes SAP HTTP callout inside a trigger (cannot do DML after callout)
trigger OrderTrigger on Opportunity (after update) {
    for (Opportunity opp : Trigger.new) {
        if (opp.StageName == 'Closed Won') {
            Http h = new Http();
            HttpRequest req = new HttpRequest();
            req.setEndpoint('https://sap-host/odata/...');
            h.send(req); // Callout in trigger context = BAD
        }
    }
}
```

### Correct: Use Platform Events or @future/Queueable for async callouts

```apex
// GOOD — fire Platform Event; middleware picks it up
trigger OrderTrigger on Opportunity (after update) {
    List<Order_Created__e> events = new List<Order_Created__e>();
    for (Opportunity opp : Trigger.new) {
        if (opp.StageName == 'Closed Won' &&
            Trigger.oldMap.get(opp.Id).StageName != 'Closed Won') {
            events.add(new Order_Created__e(
                Opportunity_Id__c = opp.Id,
                Account_Id__c = opp.AccountId,
                Amount__c = opp.Amount
            ));
        }
    }
    if (!events.isEmpty()) {
        EventBus.publish(events); // Async, no callout limit issue
    }
}
```

### Wrong: Hardcoding SAP Material IDs without padding

```python
# BAD — SAP returns "Material not found" for unpadded IDs
material_id = "MAT001"
url = f"{SAP_HOST}/API_PRODUCT_SRV/A_Product('{material_id}')"
# Returns 404 — SAP expects '000000000000MAT001'
```

### Correct: Always pad Material IDs to 18 characters

```python
# GOOD — pad with leading zeros to match SAP internal format
material_id = "MAT001".zfill(18)  # '000000000000MAT001'
url = f"{SAP_HOST}/API_PRODUCT_SRV/A_Product('{material_id}')"
# Returns 200 with material data
```

## Common Pitfalls

- **Sandbox vs production API limits differ**: Salesforce sandboxes have 5,000,000 API calls/24h (much higher than production). Load tests pass in sandbox but fail in production. Fix: `Test with production-equivalent call volumes; calculate expected daily API consumption before go-live`. [src2]
- **SAP API version not pinned**: SAP S/4HANA Cloud upgrades quarterly. An OData service behavior may change. Fix: `Pin your integration to a specific Communication Scenario version; test after each SAP upgrade cycle`. [src1]
- **Ignoring SAP field-level authorizations**: The Communication User may not have authorization for all Business Partner fields. OData returns 200 but omits restricted fields silently (no error). Fix: `Test every mapped field individually; check authorization objects S_BUPA_RFC, S_TABU_DIS`. [src1]
- **Not handling partial success in Salesforce Composite API**: Composite API can return 200 overall while individual subrequests fail. Fix: `Check httpStatusCode of each subrequest result; implement per-record error handling`. [src2]
- **Salesforce record type mismatch**: If Salesforce Account has Record Types and your integration creates records without specifying the Record Type, it uses the integration user's default — which may not be what business expects. Fix: `Explicitly set RecordTypeId in every create/upsert payload`. [src2]
- **SAP number ranges not configured for external assignment**: If you try to create a Business Partner with an externally-assigned number but the number range is set to internal, SAP rejects the request. Fix: `Verify number range settings in SAP (transaction BUCF) before designing the mapping; prefer internal numbering and store the SAP-assigned number back in Salesforce`. [src1]
- **Timezone mismatch in date comparisons**: Salesforce datetimes are always UTC. SAP S/4HANA Cloud stores in UTC, but on-premise may use the application server timezone. Delta queries using `LastChangeDateTime` can miss records if timezones are not aligned. Fix: `Always convert to UTC before comparison; store last-sync timestamp in UTC`. [src4]

## Diagnostic Commands

```bash
# Check Salesforce API usage / remaining limits
curl -s -H "Authorization: Bearer ${SF_TOKEN}" \
  "${SF_INSTANCE}/services/data/v62.0/limits/" | \
  python3 -c "import sys,json; d=json.load(sys.stdin); \
  print(f\"Daily API: {d['DailyApiRequests']['Remaining']}/{d['DailyApiRequests']['Max']}\"); \
  print(f\"Concurrent: {d['ConcurrentAsyncGetReportInstances']['Remaining']}/{d['ConcurrentAsyncGetReportInstances']['Max']}\")"

# Test SAP OData service metadata (no auth needed for $metadata on some configs)
curl -s -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/\$metadata" | \
  head -50  # Should return EDMX XML schema

# Verify SAP Communication Arrangement is active
curl -s -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?\$top=1&\$select=BusinessPartner"
# Should return 200 with at least the BusinessPartner field

# Check Salesforce Connected App token info
curl -s -H "Authorization: Bearer ${SF_TOKEN}" \
  "${SF_INSTANCE}/services/oauth2/introspect" \
  -d "token=${SF_TOKEN}&client_id=${CONSUMER_KEY}&client_secret=${CONSUMER_SECRET}"

# Monitor SAP OData request performance
curl -s -o /dev/null -w "HTTP %{http_code} | Time: %{time_total}s | Size: %{size_download} bytes\n" \
  -u "${SAP_USER}:${SAP_PASS}" \
  "https://${SAP_HOST}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?\$top=10"
```

## Version History & Compatibility

### Salesforce API Versions

| API Version | Release | Status | Key Changes for Integration |
|---|---|---|---|
| v62.0 | Spring '26 (Feb 2026) | Current | Latest composite API improvements |
| v61.0 | Winter '26 (Oct 2025) | Supported | Enhanced CDC replay capabilities |
| v60.0 | Spring '24 (Feb 2024) | Supported | Deprecated legacy SOAP partner endpoints |
| v58.0 | Spring '23 (Feb 2023) | Supported | Introduced Client Credentials flow |
| v55.0 | Spring '22 (Feb 2022) | EOL announced | Minimum for Composite API subrequest limits increase |

[src2]

### SAP S/4HANA API Versions

| Release | Date | Status | Key Changes for Integration |
|---|---|---|---|
| Cloud 2502 | Feb 2026 | Current | New OData V4 Sales Order API (API_SALESORDER_0001) |
| 2408 (On-Premise) | Aug 2024 | Current LTS | Expanded OData V4 service catalog |
| Cloud 2308 | Aug 2023 | Supported | Enhanced Business Partner OData V4 service |
| 2021 FPS02 (On-Premise) | 2022 | Supported | Minimum recommended for OData V4 |
| 1909 (On-Premise) | Sep 2019 | Maintenance | OData V2 only; V4 services limited |

[src1, src4]

### Deprecation Policy

Salesforce supports API versions for a minimum of 3 years; versions are retired in groups (check the Salesforce API version lifecycle page). SAP follows a 5-year maintenance commitment for on-premise releases and provides quarterly updates for Cloud — deprecated APIs are marked in SAP API Business Hub with replacement recommendations. [src1, src2]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Bidirectional customer/order sync between Salesforce CRM and SAP S/4HANA ERP | Only reading SAP data in Salesforce (no writes) | Salesforce Connect (OData external objects) — zero-copy, read-only |
| Real-time order creation triggered by Salesforce opportunity close | Batch migration of historical data (>100K records) | SAP Data Migration Cockpit or SAP LT Replication Server |
| Event-driven sync with delivery/fulfillment status updates | Simple one-time data load | SAP Data Services or Informatica |
| Multi-object orchestration (account + order + line items in one flow) | SAP ECC (pre-S/4HANA) without OData services enabled | SAP ECC integration card (RFC/IDoc-centric approach) |
| Organizations running both Salesforce and SAP as strategic platforms | Lightweight CRM with <50 users — no ERP needed | Salesforce-only solution or lightweight connector |

## Cross-System Comparison

| Capability | Salesforce (API v62.0) | SAP S/4HANA (2408 / Cloud 2502) | Notes |
|---|---|---|---|
| API Style | REST (native) + SOAP | OData V2/V4 (REST-like) + SOAP + RFC | SAP OData has SAP-specific query conventions |
| Rate Limits | 100K+/24h (edition-dependent), hard cap | ~100 req/s default (Cloud), configurable | Salesforce per-day; SAP per-second |
| Bulk Import | Bulk API 2.0 (150MB/file, async) | OData $batch (100 changesets) or IDoc | Salesforce bulk is more mature for large volumes |
| Event-Driven | Platform Events, CDC, Streaming API | SAP Event Mesh, Business Events | Both support pub/sub; different protocols |
| Authentication | OAuth 2.0 (multiple flows) | OAuth 2.0 + CSRF + Basic Auth + X.509 | SAP requires CSRF overlay for writes |
| Sandbox Support | Full + Partial sandbox copies | Quality/Dev systems (not production copy by default) | SF sandboxes easier to create |
| API Versioning | Numbered (v62.0), 3+ year support | Release-based (2408), OData service versioning | SF more predictable; SAP version = release |
| API Discovery | /services/data/ endpoint | SAP API Business Hub (api.sap.com) | SAP has better API catalog; SF has better limits API |
| Concurrent Requests | 25 (production) | Configurable via API Management | SF has hard cap; SAP is tunable |

## Important Caveats

- **Salesforce edition determines everything**: Enterprise Edition gets 100K + 1,000/license API calls; Unlimited gets 100K + 5,000/license. Professional Edition has no API access by default. Always verify your edition before designing the integration. [src2]
- **SAP S/4HANA Cloud vs On-Premise are different beasts**: Cloud Public Edition restricts custom ABAP (no custom RFC modules); On-Premise gives full ABAP access. Your integration architecture may differ fundamentally based on deployment model. [src1]
- **Middleware licensing costs are significant**: MuleSoft and SAP CPI are not free. MuleSoft pricing is typically $50K-150K/year; SAP CPI may be included in your S/4HANA Cloud license or charged separately. Factor this into TCO. [src7]
- **SAP rate limits are not publicly documented**: Unlike Salesforce (which publishes exact numbers per edition), SAP S/4HANA Cloud rate limits are tenant-specific and managed by SAP. The ~100 req/s figure is an approximation — contact SAP support for your tenant's actual limits. [src4]
- **All rate limits and API endpoints in this card are subject to change**: Salesforce releases 3x/year; SAP Cloud releases quarterly. Always verify against current release notes before production deployment. [src1, src2]

## Related Units

- [Salesforce REST API capabilities and rate limits](/business/erp-integration/salesforce-rest-api/2026)
- [SAP S/4HANA OData API capabilities and rate limits](/business/erp-integration/sap-s4hana-odata-api/2026)
- [Lead-to-cash integration playbook: Salesforce to SAP](/business/erp-integration/lead-to-cash-salesforce-sap/2026)
- [Salesforce + SAP ECC integration (legacy RFC/IDoc approach)](/business/erp-integration/salesforce-sap-ecc-integration/2026)
- [Salesforce Connect (OData external objects)](/business/erp-integration/salesforce-connect-odata/2026)
