---
# === IDENTITY ===
id: business/erp-integration/erp-rest-api-comparison/2026
canonical_question: "How do REST API capabilities compare across Salesforce, SAP, Oracle ERP Cloud, NetSuite, and Dynamics 365?"
aliases:
  - "ERP API comparison Salesforce SAP Oracle NetSuite Dynamics 365"
  - "Which ERP has the best REST API for integration?"
  - "REST API rate limits across major ERP platforms"
  - "ERP integration API capabilities side-by-side"
entity_type: erp_integration
domain: business > erp-integration > erp-rest-api-comparison
region: global
jurisdiction: global
temporal_scope: 2025-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Salesforce"
    vendor: "Salesforce"
    version: "API v62.0 (Spring '26)"
    edition: "Enterprise, Unlimited, Developer"
    deployment: cloud
    api_surface: "REST, SOAP, Bulk API 2.0, Streaming, Composite"
  - name: "SAP S/4HANA Cloud"
    vendor: "SAP"
    version: "2408 / 2502"
    edition: "Public Cloud, Private Cloud"
    deployment: cloud
    api_surface: "OData V2/V4, SOAP, RFC/BAPI"
  - name: "Oracle ERP Cloud (Fusion)"
    vendor: "Oracle"
    version: "24D / 25A"
    edition: "Standard, Enterprise"
    deployment: cloud
    api_surface: "REST, SOAP, FBDI, BI Publisher"
  - name: "Oracle NetSuite"
    vendor: "Oracle"
    version: "2025.1"
    edition: "Standard, Premium, Enterprise, Ultimate"
    deployment: cloud
    api_surface: "REST, SuiteTalk SOAP, SuiteQL, RESTlets"
  - name: "Microsoft Dynamics 365 Finance & Operations"
    vendor: "Microsoft"
    version: "10.0.40+"
    edition: "Finance, Supply Chain Management"
    deployment: cloud
    api_surface: "OData V4, Custom Services, Data Management Framework"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.82
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "Salesforce v62.0 (Spring '26), SAP 2502, Oracle 25A, D365 10.0.40"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Salesforce: 100,000 REST API calls per 24h (Enterprise), 15,000 (Developer) — hard cap, not burstable"
  - "SAP S/4HANA Cloud: Rate limits are tenant-specific and not publicly documented — contact SAP support for exact numbers"
  - "Oracle ERP Cloud: REST API POST limited to 500 records per request — use FBDI for bulk imports"
  - "NetSuite: Concurrency limited to 5-20 concurrent requests by service tier (Standard: 5, Premium: 15) — shared across all integrations"
  - "Dynamics 365 F&O: 6,000 requests per 5-minute sliding window per user per web server — resource-based throttling can override"
  - "All platforms: OAuth 2.0 tokens expire — refresh logic is mandatory for unattended integrations"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs detailed Salesforce-only API reference with code examples"
    use_instead: "business/erp-integration/salesforce-rest-api/2026"
  - condition: "User needs SAP-only integration patterns including RFC/BAPI"
    use_instead: "business/erp-integration/sap-s4hana-api/2026"
  - condition: "User needs iPaaS middleware comparison (MuleSoft, Boomi, Workato)"
    use_instead: "business/erp-integration/ipaas-comparison/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 this ERP)"
      - "outbound (reading from this ERP)"
      - "bidirectional sync"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/erp-rest-api-comparison/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 deep-dive (single-system card)"
    - id: "business/erp-integration/sap-s4hana-api/2026"
      label: "SAP S/4HANA OData API deep-dive"
  solves:
    - id: "business/erp-integration/erp-selection-api-criteria/2026"
      label: "ERP selection guide focusing on API capabilities"
  alternative_to:
    - id: "business/erp-integration/ipaas-comparison/2026"
      label: "iPaaS middleware comparison (MuleSoft vs Boomi vs Workato)"
  often_confused_with:
    - id: "business/erp-integration/erp-soap-api-comparison/2026"
      label: "SOAP API comparison (different protocol surface)"

# === SOURCES ===
sources:
  - id: src1
    title: "API Request Limits and Allocations"
    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-15
    reliability: authoritative
  - id: src2
    title: "Bulk API 2.0 and Bulk API Limits and Allocations"
    author: Salesforce
    url: https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_bulkapi.htm
    type: official_docs
    published: 2025-10-15
    reliability: authoritative
  - id: src3
    title: "Service Protection API Limits — Dynamics 365 Finance & Operations"
    author: Microsoft
    url: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/service-protection-api-limits
    type: official_docs
    published: 2026-01-20
    reliability: authoritative
  - 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-06-01
    reliability: moderate_high
  - id: src5
    title: "NetSuite API Governance: Concurrency & Rate Limits Explained"
    author: Houseblend
    url: https://www.houseblend.io/articles/netsuite-api-governance-guide
    type: technical_blog
    published: 2025-08-01
    reliability: moderate_high
  - id: src6
    title: "Oracle ERP REST API Guide 2025"
    author: Oracle (community)
    url: https://oracle.teknobgt.com/oracle-erp-rest-api-guide
    type: technical_blog
    published: 2025-04-01
    reliability: moderate
  - id: src7
    title: "SAP S/4HANA Cloud OData V2 API"
    author: SAP
    url: https://api.sap.com/package/SAPS4HANACloud/odata
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src8
    title: "Salesforce Governor Limits 2025: Complete Guide"
    author: Niloy Kanti Paul
    url: https://medium.com/@dev-nkp/salesforce-governor-limits-2025-complete-guide-to-understanding-and-managing-platform-boundaries-b52f886dc995
    type: community_resource
    published: 2025-03-01
    reliability: moderate_high
---

# ERP REST API Comparison: Salesforce vs SAP vs Oracle ERP Cloud vs NetSuite vs Dynamics 365

## TL;DR

- **Bottom line**: Salesforce has the most mature and well-documented REST API with the hardest rate limits; SAP uses OData (V2/V4) with undocumented tenant-specific throttling; Oracle ERP Cloud REST is limited (500 records/POST) and relies on FBDI for bulk; NetSuite shares concurrency across all integrations (5-20 slots); Dynamics 365 F&O uses resource-based throttling with 6,000 requests per 5-minute window.
- **Key limit**: Salesforce — 100,000 API calls/24h (Enterprise). NetSuite — 5-20 concurrent requests shared across all integrations. D365 — 6,000 requests per 5min per user per web server. SAP and Oracle — not publicly documented with fixed numbers.
- **Watch out for**: NetSuite concurrency is account-wide (not per-integration), Salesforce governor limits are per-transaction (triggers cascade), D365 resource-based throttling can kick in before user-based limits, Oracle REST POST cap at 500 records forces FBDI for bulk.
- **Best for**: Side-by-side evaluation when selecting an ERP or designing a multi-ERP integration architecture — tells you which API surfaces to use for which data volume and pattern.
- **Authentication**: All five support OAuth 2.0. SAP additionally supports SAML 2.0 and client certificates. NetSuite has Token-Based Authentication (TBA). Oracle supports basic auth (legacy). D365 uses Microsoft Entra ID (Azure AD).

## System Profile

This card compares REST API capabilities across the five most widely deployed enterprise ERP platforms as of early 2026. It covers cloud deployments for all five systems. On-premise variants (SAP ECC, Oracle E-Business Suite, Dynamics AX) have fundamentally different API architectures and are not covered here. Salesforce is included because its CRM platform is a dominant integration counterpart for all four ERP systems and offers ERP-adjacent capabilities through Manufacturing Cloud and Revenue Cloud.

| System | Role | API Surface | Deployment |
|---|---|---|---|
| Salesforce (v62.0) | CRM + ERP-adjacent | REST, SOAP, Bulk 2.0, Streaming, Composite | Cloud |
| SAP S/4HANA Cloud (2408/2502) | ERP — financials + SCM + manufacturing | OData V2/V4, SOAP, RFC/BAPI | Cloud |
| Oracle ERP Cloud / Fusion (24D/25A) | ERP — financials + procurement + projects | REST, SOAP, FBDI, BI Publisher | Cloud |
| Oracle NetSuite (2025.1) | ERP — mid-market financials + CRM + ecommerce | REST, SuiteTalk SOAP, SuiteQL, RESTlets | Cloud |
| Microsoft Dynamics 365 F&O (10.0.40+) | ERP — financials + SCM | OData V4, Custom Services, DMF, Dual-Write | Cloud |

## API Surfaces & Capabilities

| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| **Salesforce REST** | HTTPS/JSON | Individual record CRUD, <2K records | 2,000 (query), 200 (composite) | 100K calls/24h (Enterprise) | Yes | No |
| **Salesforce Bulk API 2.0** | HTTPS/CSV | ETL, data migration, >2K records | 150M per file | 15K batches/24h | No | Yes |
| **SAP OData V4** | HTTPS/JSON | Modern integrations, read/write | 1,000 per page (default) | ~100 req/s (typical), tenant-specific | Yes | Via batch |
| **SAP OData V2** | HTTPS/JSON+XML | Legacy integrations, broad coverage | 1,000 per page | Shared with V4 | Yes | Via batch |
| **Oracle ERP Cloud REST** | HTTPS/JSON | Financial CRUD, procurement, projects | 500 per POST | Not publicly documented | Yes | No |
| **Oracle FBDI** | File/CSV | Bulk imports (AP, AR, GL, PO) | 500MB per file | Job queue-based | No | Yes |
| **NetSuite REST** | HTTPS/JSON | Record CRUD, newer integrations | 1,000 (query), 1 (write) | 5-20 concurrent (tier) | Yes | No |
| **NetSuite SuiteQL** | HTTPS/JSON | Complex queries, analytics | 100,000 rows per query | Shared concurrency | Yes | Read-only |
| **D365 F&O OData** | HTTPS/JSON | Entity CRUD, real-time integration | 10,000 per page | 6K/5min per user per server | Yes | No |
| **D365 DMF** | File/Package | Data migration, recurring imports | Package-based | Exempt from API limits | No | Yes |

[src1, src2, src3, src4, src5, src6]

## Rate Limits & Quotas

<!-- This is the highest-value section. Agents hallucinate rate limits constantly.
     Every number must be cited. Edition-specific differences are critical. -->

### Per-Request Limits

| System | Limit Type | Value | Notes |
|---|---|---|---|
| Salesforce | Max records per query | 2,000 | Use queryMore() for pagination [src1] |
| Salesforce | Max composite subrequests | 25 | All-or-nothing by default [src1] |
| Salesforce | Max request body size | 50 MB | REST API [src1] |
| Salesforce | Max batch file size | 150 MB | Bulk API 2.0 [src2] |
| SAP S/4HANA | Max records per page | 1,000 | OData $top default; adjustable [src4] |
| SAP S/4HANA | Batch request operations | 100-1,000 | Multipart/mixed boundaries [src4] |
| Oracle ERP Cloud | Max records per POST | 500 | Use FBDI for larger volumes [src6] |
| Oracle ERP Cloud | Max FBDI file size | 500 MB | Split logically for performance [src6] |
| NetSuite | Max query results | 1,000 | Per request; paginate for more [src5] |
| NetSuite | Max SuiteQL rows | 100,000 | Per query execution [src5] |
| D365 F&O | Max records per page | 10,000 | OData $top parameter [src3] |

### Rolling / Daily Limits

| System | Limit Type | Value | Window | Edition Differences |
|---|---|---|---|---|
| Salesforce | API calls | 100,000 | 24h rolling | Enterprise: 100K, Unlimited: 5M, Developer: 15K [src1] |
| Salesforce | Bulk API batches | 15,000 | 24h rolling | Shared between Bulk API and Bulk API 2.0 [src2] |
| SAP S/4HANA | API throughput | ~100 req/s default | Per second | Tenant-specific; varies by subscription tier [src4] |
| SAP S/4HANA | Burst capacity | ~200 req/s | Per second | Short burst only [src4] |
| Oracle ERP Cloud | REST API rate | Not published | N/A | Contact Oracle support for tenant limits [src6] |
| NetSuite | Daily calls | ~100K-500K (estimated) | 24h | Not officially published; tier-dependent [src5] |
| NetSuite | Concurrent requests | 5-20 | Continuous | Standard: 5, Premium: 15, Enterprise/Ultimate: 20. +10 per SuiteCloud Plus license [src5] |
| D365 F&O | Requests per user | 6,000 | 5-min sliding window | Per user, per application ID, per web server [src3] |
| D365 F&O | Execution time | 1,200 seconds | 5-min sliding window | Combined execution time per user [src3] |
| D365 F&O | Concurrent requests | 52 | Continuous | Per user per web server [src3] |

### Transaction / Governor Limits

Salesforce is unique among the five in enforcing per-transaction governor limits within Apex code execution. These limits cascade through triggers — a common source of integration failures. [src8]

| Limit Type | Salesforce (Sync) | Salesforce (Async) | NetSuite (Governance Units) |
|---|---|---|---|
| SOQL/SuiteQL queries | 100 | 200 | N/A (concurrency-based) |
| Records retrieved (query) | 50,000 | 50,000 | 1,000 per page |
| DML statements | 150 | 150 | N/A |
| Records processed (DML) | 10,000 | 10,000 | N/A |
| CPU time | 10,000 ms | 60,000 ms | N/A |
| Heap size | 6 MB | 12 MB | N/A |
| HTTP callouts | 100 | 100 | N/A |

SAP, Oracle ERP Cloud, and Dynamics 365 F&O do not have per-transaction governor limits comparable to Salesforce. Their throttling is purely rate-based (requests per time window or concurrent connections). [src3, src4, src6]

## Authentication

| System | Primary Flow | Server-to-Server | User Context | Token Lifetime | MFA Support |
|---|---|---|---|---|---|
| Salesforce | OAuth 2.0 | JWT Bearer flow | Web Server flow | Access: ~2h, Refresh: until revoked | Yes (required for production) |
| SAP S/4HANA | OAuth 2.0 | Client Credentials | Authorization Code | Configurable via Communication Arrangements | SAML 2.0, mTLS |
| Oracle ERP Cloud | OAuth 2.0 | Client Credentials | Authorization Code | Short-lived (vendor-managed) | Yes |
| NetSuite | Token-Based Auth (TBA) | TBA (consumer + token pairs) | OAuth 2.0 (newer) | TBA: does not expire | OAuth tokens expire |
| D365 F&O | Microsoft Entra ID | App Registration (client secret/cert) | Delegated (on-behalf-of) | Access: 1h, Refresh: 90 days | Entra ID Conditional Access |

[src1, src3, src4, src5, src6]

### Authentication Gotchas

- **Salesforce**: JWT bearer flow requires a connected app with a digital certificate uploaded. Self-signed works for dev; CA-signed recommended for production. The integration user's permissions define all API access — not the certificate holder. [src1]
- **SAP S/4HANA**: Communication Arrangements must be configured in the Fiori launchpad before any external system can authenticate. This is a common onboarding blocker that has no API equivalent. [src4]
- **Oracle ERP Cloud**: Basic auth (username:password) still works and appears in many tutorials — do NOT use this in production. OAuth 2.0 is required for security and audit compliance. [src6]
- **NetSuite**: TBA tokens do not expire, which is convenient but a security risk. Rotate periodically and bind to a dedicated integration role, not a personal user. [src5]
- **D365 F&O**: App registrations in Microsoft Entra ID require explicit API permissions for Dynamics 365. Missing the `Dynamics ERP` permission scope is a common first-time setup failure. [src3]

## Constraints
<!-- Agents: read this section before recommending any integration approach from this unit.
     These are hard constraints that must not be violated. -->

- **Salesforce 100K/24h hard cap**: Cannot be increased without purchasing API call add-ons. At Enterprise edition, 100K calls sounds generous until you account for multiple integrations sharing the pool. Plan for 50-70% utilization ceiling. [src1]
- **NetSuite shared concurrency**: 5 concurrent request slots on Standard tier are shared across ALL integrations (SOAP, REST, RESTlets). A single poorly-throttled integration can block all others. [src5]
- **Oracle ERP Cloud 500-record POST limit**: REST API is not suitable for bulk data operations. FBDI is the only performant path for loading >500 records, and FBDI requires file upload to UCM + asynchronous processing (not REST). [src6]
- **SAP undocumented limits**: SAP does not publish fixed rate limit numbers for S/4HANA Cloud. Limits are tenant-specific and adjusted by SAP support. This makes capacity planning difficult without a test run against your actual tenant. [src4]
- **D365 resource-based throttling**: Even if you stay within the 6,000 requests per 5-minute user-based limit, resource-based throttling can kick in based on aggregate server CPU/memory utilization across all users. This is unpredictable. [src3]
- **Cross-platform token management**: All five platforms use different token lifecycles. A multi-ERP integration must handle five different refresh patterns — there is no standard. [src1, src3, src4, src5, src6]

## Integration Pattern Decision Tree

```
START — Choosing an ERP API surface for integration
├── What's your data volume per operation?
│   ├── < 500 records
│   │   ├── Salesforce → REST API (Composite for multi-object)
│   │   ├── SAP S/4HANA → OData V4 (single or batch)
│   │   ├── Oracle ERP Cloud → REST API
│   │   ├── NetSuite → REST API or RESTlet
│   │   └── D365 F&O → OData V4
│   ├── 500-10,000 records
│   │   ├── Salesforce → Bulk API 2.0
│   │   ├── SAP S/4HANA → OData batch request
│   │   ├── Oracle ERP Cloud → FBDI (file-based)
│   │   ├── NetSuite → SuiteTalk SOAP (lists) or CSV import
│   │   └── D365 F&O → Data Management Framework (DMF)
│   └── > 10,000 records
│       ├── Salesforce → Bulk API 2.0 (split jobs if >150MB)
│       ├── SAP S/4HANA → IDocs or BAPI batch
│       ├── Oracle ERP Cloud → FBDI (split files for >250MB)
│       ├── NetSuite → CSV Import or SuiteCloud Plus + SOAP
│       └── D365 F&O → Data Management Framework (recurring)
├── What's the latency requirement?
│   ├── Real-time (<1s)
│   │   ├── Salesforce → REST API
│   │   ├── SAP → OData V4
│   │   ├── Oracle → REST API (small payloads only)
│   │   ├── NetSuite → REST/RESTlet (within concurrency limit)
│   │   └── D365 → OData V4
│   └── Near-real-time or scheduled
│       ├── Salesforce → Platform Events + Bulk API 2.0
│       ├── SAP → Business Events + OData batch
│       ├── Oracle → FBDI + ESS scheduler
│       ├── NetSuite → Saved Search + scheduled SuiteScript
│       └── D365 → DMF recurring + Business Events
└── Event-driven (push notifications)?
    ├── Salesforce → Platform Events, Change Data Capture (CDC)
    ├── SAP → Business Events (2408+), Event Mesh
    ├── Oracle → Business Events (limited), Oracle Integration webhooks
    ├── NetSuite → User Event Scripts, Workflow triggers
    └── D365 → Business Events, Dual-Write (to Dataverse)
```

## Quick Reference

### Capability Comparison Matrix

| Capability | Salesforce | SAP S/4HANA | Oracle ERP Cloud | NetSuite | D365 F&O | Winner |
|---|---|---|---|---|---|---|
| REST API maturity | Excellent | Good (OData) | Moderate | Good | Good (OData) | Salesforce |
| API documentation | Excellent | Good | Moderate | Moderate | Very Good | Salesforce |
| Rate limit transparency | Excellent (published) | Poor (undocumented) | Poor (undocumented) | Moderate | Excellent (published) | Salesforce/D365 |
| Bulk data import | Bulk API 2.0 (150MB/file) | IDocs/BAPI batch | FBDI (500MB/file) | CSV Import, SOAP lists | DMF packages | Oracle (file size) |
| Real-time events | Platform Events + CDC | Business Events (newer) | Limited | User Event Scripts | Business Events + Dual-Write | Salesforce |
| Query language | SOQL/SOSL | OData $filter | REST query params | SuiteQL (SQL-like) | OData $filter | NetSuite (SuiteQL) |
| Max concurrent requests | No hard limit (rate-based) | Tenant-specific | Not published | 5-20 (tier) | 52 per user per server | D365 |
| Sandbox/test environment | Full + Partial sandbox | Test tenant | Test instance | Sandbox (limited) | Sandbox + UAT | Salesforce |
| API versioning | Numbered (v62.0) | Release-based (2408) | Release-based (24D) | WSDL versioned | Platform version | Salesforce |
| Webhook/callback support | Outbound Messages, Platform Events | Business Events | Business Events (limited) | User Event + Workflow | Business Events | Salesforce |
| GraphQL support | No (planned) | No | No | No | No | None |
| Pagination | Cursor (queryMore) | $skip/$top | offset/limit | offset/limit (REST), cursor (SOAP) | $skip/$top | Salesforce (cursor) |

[src1, src2, src3, src4, src5, src6]

## Step-by-Step Integration Guide

### 1. Test authentication across all five platforms

Each platform requires a different setup process. Start by validating authentication independently before building integration logic. [src1, src3, src4, src5, src6]

```bash
# Salesforce: OAuth 2.0 JWT Bearer flow
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}"

# SAP S/4HANA: OAuth 2.0 Client Credentials
curl -X POST https://{tenant}.authentication.{region}.hana.ondemand.com/oauth/token \
  -d "grant_type=client_credentials" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}"

# Oracle ERP Cloud: OAuth 2.0
curl -X POST https://{instance}.fa.{dc}.oraclecloud.com/oauth2/v1/token \
  -d "grant_type=client_credentials" \
  -d "scope=urn:opc:resource:consumer::all" \
  -u "${CLIENT_ID}:${CLIENT_SECRET}"

# NetSuite: Token-Based Authentication (TBA)
# Uses OAuth 1.0 signature — headers include consumer key, token, nonce, timestamp
curl -X GET "https://{account_id}.suitetalk.api.netsuite.com/services/rest/record/v1/customer" \
  -H "Authorization: OAuth realm=\"{account_id}\",oauth_consumer_key=\"...\",oauth_token=\"...\",oauth_signature_method=\"HMAC-SHA256\",oauth_timestamp=\"...\",oauth_nonce=\"...\",oauth_version=\"1.0\",oauth_signature=\"...\""

# D365 F&O: Microsoft Entra ID (Azure AD)
curl -X POST https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token \
  -d "grant_type=client_credentials" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "scope=https://{environment}.operations.dynamics.com/.default"
```

**Verify**: Each curl should return a JSON body with `access_token`. HTTP 200 = success, 401 = credential issue, 400 = wrong grant type or scope.

### 2. Query records using each platform's REST API

Each platform has a different query syntax. This step validates read access and shows the response shape. [src1, src4, src6, src5, src3]

```bash
# Salesforce: SOQL via REST
curl https://{instance}.my.salesforce.com/services/data/v62.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+10 \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

# SAP S/4HANA: OData V4 with $filter
curl "https://{tenant}.s4hana.cloud.sap/sap/opu/odata4/sap/api_business_partner/srvd_a2x/sap/businesspartner/0001/A_BusinessPartner?\$top=10&\$filter=BusinessPartnerCategory eq '1'" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

# Oracle ERP Cloud: REST with query params
curl "https://{instance}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/latest/invoices?limit=10&q=InvoiceAmount>1000" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

# NetSuite: SuiteQL
curl -X POST "https://{account_id}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql" \
  -H "Content-Type: application/json" \
  -H "Prefer: transient" \
  -d '{"q": "SELECT id, companyname FROM customer WHERE isinactive = '\''F'\'' FETCH FIRST 10 ROWS ONLY"}' \
  -H "Authorization: OAuth ..."

# D365 F&O: OData with $filter
curl "https://{environment}.operations.dynamics.com/data/Customers?\$top=10&\$filter=CustomerGroupId eq '10'" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"
```

**Verify**: HTTP 200 with JSON array of records. Check `totalSize` (Salesforce), `@odata.count` (SAP/D365), `totalResults` (NetSuite) for total available records.

### 3. Create/update a record on each platform

Write operations test permissions, field validation, and response format. [src1, src4, src6, src5, src3]

```bash
# Salesforce: Create Account
curl -X POST https://{instance}.my.salesforce.com/services/data/v62.0/sobjects/Account \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"Name": "Test Integration Account"}'
# Returns: {"id": "001...", "success": true}

# SAP S/4HANA: Create Business Partner (OData V4)
curl -X POST "https://{tenant}.s4hana.cloud.sap/sap/opu/odata4/sap/api_business_partner/srvd_a2x/sap/businesspartner/0001/A_BusinessPartner" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"BusinessPartnerCategory": "1", "BusinessPartnerFullName": "Test Partner"}'

# Oracle ERP Cloud: Create Invoice
curl -X POST "https://{instance}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/latest/invoices" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"InvoiceNumber": "TEST-001", "InvoiceAmount": 100.00}'

# NetSuite: Create Customer
curl -X POST "https://{account_id}.suitetalk.api.netsuite.com/services/rest/record/v1/customer" \
  -H "Content-Type: application/json" \
  -H "Authorization: OAuth ..." \
  -d '{"companyName": "Test Integration Customer", "subsidiary": {"id": "1"}}'

# D365 F&O: Create Customer
curl -X POST "https://{environment}.operations.dynamics.com/data/Customers" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"CustomerAccount": "TEST001", "CustomerGroupId": "10", "Name": "Test Customer"}'
```

**Verify**: HTTP 201 (Created) for all platforms. Salesforce returns `success: true` + ID. SAP/Oracle/D365 return the full created entity. NetSuite returns a `Location` header with the record URL.

### 4. Handle pagination for large result sets

Each platform paginates differently. Production integrations must handle this correctly. [src1, src4, src5, src3]

```bash
# Salesforce: Cursor-based (queryMore)
# Initial query returns nextRecordsUrl if results > 2,000
curl https://{instance}.my.salesforce.com${nextRecordsUrl} \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

# SAP S/4HANA: $skip/$top (offset-based)
curl "https://{tenant}.s4hana.cloud.sap/...?\$top=1000&\$skip=1000" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

# Oracle ERP Cloud: offset/limit
curl "https://{instance}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/latest/invoices?offset=500&limit=500" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

# NetSuite: offset-based (REST) or cursor (SOAP)
curl "https://{account_id}.suitetalk.api.netsuite.com/services/rest/record/v1/customer?offset=1000&limit=1000" \
  -H "Authorization: OAuth ..."

# D365 F&O: $skip/$top or @odata.nextLink
curl "https://{environment}.operations.dynamics.com/data/Customers?\$top=10000&\$skip=10000" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"
```

**Verify**: Check for `nextRecordsUrl` (Salesforce), `@odata.nextLink` (SAP/D365), `hasMore` (NetSuite), or empty results array (all) to detect end of data.

## Data Mapping

### Cross-Platform Field Naming Conventions

| Concept | Salesforce | SAP S/4HANA | Oracle ERP Cloud | NetSuite | D365 F&O |
|---|---|---|---|---|---|
| Customer name | Account.Name | BusinessPartnerFullName | OrganizationName | companyName | Name |
| Customer ID | Account.Id (18-char) | BusinessPartner (10-digit) | PartyId (numeric) | id (internalId) | CustomerAccount |
| Invoice number | Invoice.InvoiceNumber | BillingDocument | InvoiceNumber | tranId | InvoiceId |
| Amount field | Amount (decimal) | NetAmount (smallest unit) | InvoiceAmount (decimal) | amount (decimal) | InvoiceAmount (decimal) |
| Date format | YYYY-MM-DD (UTC) | YYYY-MM-DD (UTC) | YYYY-MM-DD | YYYY-MM-DD | YYYY-MM-DD (UTC) |
| Boolean | true/false | true/false | true/false | true/false (or "T"/"F") | NoYes enum (0/1) |
| Currency | CurrencyIsoCode | TransactionCurrency | InvoiceCurrencyCode | currency.id (internalId) | CurrencyCode |
| Status values | Picklist (string) | Domain values (string) | Lookup (string) | list values (string/id) | Enum (integer) |

### Data Type Gotchas

- **SAP amounts in smallest currency unit**: SAP stores amounts in the smallest currency unit (e.g., cents for USD). Divide by 100 before mapping to other systems that use decimal amounts. Forgetting this creates 100x overcharges. [src4]
- **Salesforce 18-char vs 15-char IDs**: API always returns 18-character case-insensitive IDs, but formulas may reference 15-character case-sensitive IDs. Always store and compare the 18-character version. [src1]
- **NetSuite internal vs external IDs**: NetSuite records have both `internalId` (numeric, system-assigned) and `externalId` (string, user-defined). Integrations should use `externalId` for idempotent upserts. [src5]
- **D365 enums vs strings**: D365 F&O uses integer enums for many fields that other systems store as strings (e.g., `NoYes::Yes = 1`). The OData layer exposes these as string labels, but writes may require the integer value. [src3]
- **Oracle multi-org context**: Oracle ERP Cloud REST API requires the correct Business Unit context. Queries without it may return empty results or cross-BU data unintentionally. [src6]

## Error Handling & Failure Points

### Common Error Codes

| HTTP Code | Salesforce | SAP S/4HANA | Oracle ERP Cloud | NetSuite | D365 F&O |
|---|---|---|---|---|---|
| 400 | MALFORMED_QUERY, INVALID_FIELD | /sap/opu error details | Missing required fields | INVALID_RECORD, SSS_MISSING_REQD_ARGUMENT | Validation error (OData) |
| 401 | INVALID_SESSION_ID | Token expired | Token expired | INVALID_LOGIN_CREDENTIALS | Token expired |
| 403 | INSUFFICIENT_ACCESS | No authorization for object | Insufficient privileges | SSS_NO_PERMISSION | Missing API permission |
| 404 | NOT_FOUND | Entity not found | Resource not found | RECORD_NOT_FOUND | Entity not found |
| 429 | REQUEST_LIMIT_EXCEEDED | Throttled (varies) | Rate limited | SSS_REQUEST_LIMIT_EXCEEDED | Too Many Requests + Retry-After |

[src1, src3, src4, src5, src6]

### Failure Points in Production

- **Salesforce governor limit cascade**: A trigger on Account fires SOQL queries. An integration bulk-updates 200 Accounts in one DML. Each trigger invocation shares the same 100-SOQL-query pool. At 200 records, you can easily exceed 100 queries if the trigger has subqueries. Fix: `bulkify triggers — query outside loops, use maps for lookups`. [src8]
- **NetSuite concurrency starvation**: One integration holds 14 of 15 concurrent slots for long-running SOAP calls. All other integrations (REST, RESTlets, other SOAP) are blocked with 429s. Fix: `implement the Gatekeeper pattern — queue requests and limit concurrent workers to 50-70% of your concurrency cap`. [src5]
- **D365 resource-based throttling during month-end**: Aggregate server load from all users (interactive + integration) can trigger resource-based throttling even when individual integrations are well within user-based limits. Fix: `schedule heavy integrations outside peak hours; use priority-based throttling configuration to protect critical integrations`. [src3]
- **Oracle FBDI silent failures**: FBDI import jobs can complete with status "Succeeded" while individual records fail. The error details are in the ESS job output file, not the REST response. Fix: `always download and parse the output file after FBDI import completes; never assume success from status alone`. [src6]
- **SAP Communication Arrangement expiry**: OAuth tokens in SAP S/4HANA Cloud are tied to Communication Arrangements. If the arrangement is deactivated or the user is locked, all integrations fail silently with 401. Fix: `monitor the Communication Management tile in Fiori launchpad; set up alerting on repeated 401 responses`. [src4]

## Anti-Patterns

### Wrong: Using the same API surface for all data volumes

```javascript
// BAD — Using Salesforce REST API for 50,000-record import
for (let i = 0; i < 50000; i++) {
  await fetch(`${instanceUrl}/services/data/v62.0/sobjects/Contact`, {
    method: 'POST',
    body: JSON.stringify(contacts[i])
  });
}
// 50,000 API calls = 50% of daily Enterprise limit in one operation
```

### Correct: Match API surface to data volume

```javascript
// GOOD — Use Bulk API 2.0 for large imports
const job = await fetch(`${instanceUrl}/services/data/v62.0/jobs/ingest`, {
  method: 'POST',
  body: JSON.stringify({
    object: 'Contact',
    operation: 'insert',
    contentType: 'CSV'
  })
});
// Upload CSV data to the job, consumes 1 Bulk API batch instead of 50K REST calls
```

### Wrong: Ignoring NetSuite concurrency in multi-integration architecture

```javascript
// BAD — 3 integrations each opening 5 parallel connections on Standard tier (limit: 5)
// Integration A: 5 slots, Integration B: blocked, Integration C: blocked
Promise.all(records.map(r => fetch(netsuiteUrl, { body: r })));
```

### Correct: Implement concurrency governance across all integrations

```javascript
// GOOD — Centralized concurrency pool with gatekeeper
const MAX_CONCURRENT = Math.floor(concurrencyLimit * 0.7); // Reserve 30% for other integrations
const semaphore = new Semaphore(MAX_CONCURRENT);
for (const record of records) {
  await semaphore.acquire();
  fetch(netsuiteUrl, { body: record })
    .finally(() => semaphore.release());
}
```

### Wrong: Hardcoding D365 retry logic without reading Retry-After header

```javascript
// BAD — Fixed 1-second retry on 429
if (response.status === 429) {
  await sleep(1000);
  retry();
}
```

### Correct: Respect Retry-After header with exponential backoff

```javascript
// GOOD — D365 returns Retry-After header with specific wait time
if (response.status === 429) {
  const retryAfter = parseInt(response.headers.get('Retry-After') || '5');
  await sleep(retryAfter * 1000);
  retry();
}
```

## Common Pitfalls

- **Salesforce — API version pinning**: Not pinning the API version (e.g., using `/services/data/v62.0/` explicitly) means your integration may break when Salesforce deprecates old versions. Fix: `always pin the version in your base URL; test against the next version before each Salesforce release`. [src1]
- **SAP — OData V2 vs V4 mismatch**: Many SAP tutorials and examples use OData V2, but newer APIs only expose V4 endpoints. Mixing protocols causes subtle serialization errors. Fix: `check api.sap.com for each API — the "OData Version" is labeled clearly; use V4 for all new integrations`. [src4, src7]
- **Oracle — Forgetting Business Unit context**: Oracle ERP Cloud queries may return empty results or cross-org data if the Business Unit is not specified in the request headers or query parameters. Fix: `always include the Business Unit parameter; test with a dedicated integration user scoped to a single BU`. [src6]
- **NetSuite — RESTlet per-user limit**: Individual users have a limit of 5 concurrent RESTlet executions, separate from the account-wide concurrency limit. Fix: `use a dedicated integration role and user for each integration; do not share RESTlet users across integrations`. [src5]
- **D365 — Assuming DMF exemption covers OData**: The Data Management Framework (DMF/DIXF) is exempt from service protection API limits, but OData endpoints are not. Developers sometimes mix DMF and OData patterns. Fix: `use DMF for bulk/recurring imports; use OData only for real-time, low-volume operations`. [src3]
- **All platforms — Not handling partial success**: Bulk operations on every platform can partially succeed. Salesforce Bulk API 2.0 returns per-record success/failure, Oracle FBDI buries errors in output files, NetSuite SOAP returns per-record status. Fix: `always parse the complete response/output for per-record status; implement dead-letter queues for failed records`. [src1, src2, src5, src6]

## Diagnostic Commands

```bash
# === SALESFORCE ===
# Check remaining API limits
curl https://{instance}.my.salesforce.com/services/data/v62.0/limits \
  -H "Authorization: Bearer ${SF_TOKEN}" | jq '.DailyApiRequests'

# === SAP S/4HANA ===
# Test OData metadata access (validates auth + permissions)
curl "https://{tenant}.s4hana.cloud.sap/sap/opu/odata4/sap/api_business_partner/srvd_a2x/sap/businesspartner/0001/\$metadata" \
  -H "Authorization: Bearer ${SAP_TOKEN}"

# === ORACLE ERP CLOUD ===
# Test REST API connectivity
curl "https://{instance}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/latest" \
  -H "Authorization: Bearer ${ORACLE_TOKEN}" | jq '.items[].name'

# === NETSUITE ===
# Check available record types via REST
curl "https://{account_id}.suitetalk.api.netsuite.com/services/rest/record/v1/metadata-catalog" \
  -H "Authorization: OAuth ..."

# === DYNAMICS 365 F&O ===
# List available data entities
curl "https://{env}.operations.dynamics.com/data/\$metadata" \
  -H "Authorization: Bearer ${D365_TOKEN}" | head -100

# Check if user-based throttling is hitting you (look for 429 in logs)
# D365 returns: "Number of requests exceeded the limit of 6000 over time window of 300 seconds"
```

## Version History & Compatibility

| Platform | Current Version | Release Cycle | Previous | Breaking Changes | Deprecation Policy |
|---|---|---|---|---|---|
| Salesforce | v62.0 (Spring '26) | 3x/year (Spring, Summer, Winter) | v61.0 (Winter '26) | None in v62.0 | 3+ years minimum support; retired in batches [src1] |
| SAP S/4HANA Cloud | 2502 (Feb 2026) | Quarterly (Public), biannual (Private) | 2408 (Aug 2024) | OData V2 deprecation for some APIs in 2025 | 2-year notice on SAP API Business Hub [src7] |
| Oracle ERP Cloud | 25A (Q1 2026) | Quarterly (A/B/C/D) | 24D (Q4 2025) | No breaking REST API changes | Quarterly releases; rare API removal [src6] |
| NetSuite | 2025.1 | Biannual (x.1, x.2) | 2024.2 | SuiteTalk SOAP endpoints deprecated for new dev | 2-year notice; SOAP still supported [src5] |
| D365 F&O | 10.0.40+ (2026) | Monthly (10.0.x) | 10.0.39 | User-based API limits disabled by default (10.0.36) | 12-month notice for entity deprecation [src3] |

[src1, src3, src4, src5, src6, src7]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Evaluating which ERP has the best API for your integration architecture | You've already selected an ERP and need deep API reference | Single-system API cards (Salesforce REST API, SAP S/4HANA API, etc.) |
| Designing a multi-ERP integration that spans 2+ of these platforms | You need middleware/iPaaS selection guidance | business/erp-integration/ipaas-comparison/2026 |
| Comparing rate limits and bulk capabilities to size your integration | You need specific code examples for one platform | Single-system cards with full code samples |
| Assessing authentication complexity for a new integration project | You need SOAP-only API comparison | business/erp-integration/erp-soap-api-comparison/2026 |
| Planning capacity for an integration that must stay within API limits | You need pricing/licensing comparison | business/erp-selection/erp-api-licensing/2026 |

## Cross-System Comparison

| Capability | Salesforce | SAP S/4HANA | Oracle ERP Cloud | NetSuite | D365 F&O | Notes |
|---|---|---|---|---|---|---|
| API Style | REST + SOAP + Bulk | OData V2/V4 + SOAP + RFC | REST + SOAP + FBDI | REST + SOAP + SuiteQL + RESTlets | OData V4 + Custom Services + DMF | Salesforce most diverse |
| Rate Limit Model | Fixed daily cap (100K/24h) | Tenant-specific (undocumented) | Not published | Concurrency-based (5-20 slots) | Sliding window (6K/5min/user) + resource-based | D365 most transparent model |
| Bulk Import | Bulk API 2.0 (150MB/file) | IDocs/BAPI batch | FBDI (500MB/file) | CSV Import / SOAP lists | DMF packages | Oracle largest file support |
| Event-Driven | Platform Events + CDC | Business Events (2408+) | Business Events (limited) | User Event Scripts | Business Events + Dual-Write | Salesforce most mature |
| Authentication | OAuth 2.0 (JWT, Web Server) | OAuth 2.0, SAML, mTLS, Basic | OAuth 2.0, Basic (legacy) | TBA (OAuth 1.0), OAuth 2.0 | Microsoft Entra ID (OAuth 2.0) | SAP most options |
| Sandbox | Full + Partial | Test tenant | Test instance | Sandbox (limited) | Sandbox + UAT | Salesforce most flexible |
| API Versioning | Numbered (v62.0) | Release-based (2408) | Release-based (24D) | WSDL versioned | Platform version (10.0.x) | Salesforce most explicit |
| Query Language | SOQL (proprietary) | OData $filter (standard) | REST params (simple) | SuiteQL (SQL-like) | OData $filter (standard) | NetSuite most familiar to SQL devs |
| Developer Experience | Excellent (Workbench, Postman) | Good (API Hub sandbox) | Moderate | Moderate (improving) | Very Good (Postman, VS Code) | Salesforce best DX |
| Error Messages | Detailed (error code + fields) | Moderate (OData error detail) | Basic | Moderate (SuiteScript errors are better) | Good (validation details) | Salesforce most helpful |
| Webhook Support | Outbound Messages, Platform Events | Business Events | Limited (requires OIC) | User Event + Workflow | Business Events | Salesforce most flexible |

[src1, src2, src3, src4, src5, src6, src7]

## Important Caveats

- **Edition-specific limits vary dramatically**: Salesforce Developer edition has only 15,000 API calls/24h vs 5,000,000 for Unlimited. NetSuite Standard tier has only 5 concurrent slots vs 20 for Enterprise. Always verify limits for your specific edition/tier. [src1, src5]
- **Sandbox != Production**: Salesforce sandbox has lower API limits and may not have all production data. SAP test tenants may have different rate limits. D365 sandbox environments have fewer web servers. Always load-test against realistic conditions. [src1, src3]
- **Rate limits change with each release**: Salesforce, SAP, and D365 can change API limits in any release. Oracle and NetSuite change less frequently but can adjust without notice. Always verify against current release notes before go-live. [src1, src3, src4, src5, src6]
- **This comparison covers cloud deployments only**: On-premise variants (SAP ECC, Oracle E-Business Suite, Dynamics AX/GP) have fundamentally different API architectures, limits, and authentication mechanisms. This card does not apply to those systems.
- **Salesforce is CRM-first**: While included for its API maturity and its role as a common integration counterpart, Salesforce is not a traditional ERP. Its financial and operational capabilities are narrower than the other four systems.

## Related Units

- [Salesforce REST API deep-dive](/business/erp-integration/salesforce-rest-api/2026)
- [SAP S/4HANA OData API deep-dive](/business/erp-integration/sap-s4hana-api/2026)
- [iPaaS middleware comparison (MuleSoft vs Boomi vs Workato)](/business/erp-integration/ipaas-comparison/2026)
- [ERP selection guide focusing on API capabilities](/business/erp-integration/erp-selection-api-criteria/2026)
