CPQ-to-ERP Integration: Complex Order Creation from Salesforce CPQ and Oracle CPQ

Type: ERP Integration Systems: Salesforce CPQ / Revenue Cloud, Oracle CPQ Cloud, SAP S/4HANA, NetSuite, D365 Confidence: 0.83 Sources: 7 Verified: 2026-03-07 Freshness: volatile

TL;DR

System Profile

This integration playbook covers the CPQ-to-ERP order creation segment of the quote-to-cash cycle. It addresses two major CPQ platforms — Salesforce CPQ (including Revenue Cloud) and Oracle CPQ Cloud — and four major target ERPs: SAP S/4HANA, Oracle NetSuite, Microsoft Dynamics 365 Finance & SCM, and Oracle ERP Cloud. The focus is specifically on converting approved, fully-priced quotes into structured sales orders in the ERP, including line items, pricing, discounts, shipping, and tax classification.

SystemRoleAPI SurfaceDirection
Salesforce CPQ / Revenue CloudCPQ — quote authoring, pricing, approvalsREST API v66.0, SBQQ ServiceRouter, Platform EventsOutbound (source)
Oracle CPQ CloudCPQ — configure, price, quoteREST API v19, Commerce API, Configuration APIOutbound (source)
SAP S/4HANAERP — sales order managementOData v4 (A_SalesOrder), BAPI, IDocInbound (target)
Oracle NetSuiteERP — sales order managementSuiteTalk REST/SOAP, RESTletInbound (target)
Microsoft Dynamics 365 F&SCMERP — sales order managementOData v4, Dual-write, Data EntitiesInbound (target)
iPaaS (MuleSoft / Boomi / Celigo / OIC)Middleware — orchestration, transformationPre-built connectorsOrchestrator

API Surfaces & Capabilities

Salesforce CPQ / Revenue Cloud APIs

API SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
SBQQ ServiceRouter (Apex)Apex REST / RemotingQuote calculation, save, read modelGovernor limits100 SOQL/txn, 150 DML/txnYesNo
REST API v66.0HTTPS/JSONCRUD on quote/order objects200 composite100K calls/24h (Enterprise)YesNo
Bulk API 2.0HTTPS/CSVMass quote line export150M per file15K batches/24hNoYes
Platform EventsBayeux/CometDQuote/order status notificationsN/AEdition-dependentYesN/A
Revenue Cloud Order ManagementHTTPS/JSONNative order creationPer-transactionShared with REST APIYesNo

Oracle CPQ Cloud APIs

API SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
Commerce REST API v19HTTPS/JSONTransaction CRUD, order actions50 per queryThrottled (~50 concurrent)YesNo
Configuration REST APIHTTPS/JSONProduct configuration, BOMPer-modelShared with CommerceYesNo
Bulk Data Services (24D+)HTTPS/CSVMass transaction export/importLarge batchSeparate quotaNoYes
OIC Integration (pub/sub)HTTPS/JSON + messagingEvent-driven ERP syncQueue-basedOIC limitsNear-real-timeYes

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueSystemNotes
Max composite subrequests25Salesforce Composite APIAll-or-nothing by default
Max SOQL query results2,000 per pageSalesforce REST APIUse queryMore for pagination
Max request body size50 MBSalesforce REST APIComplex bundle quotes can approach this
Max collection query results50 per pageOracle CPQ REST APIUse offset and limit
Max OData batch subrequests1,000SAP S/4HANA ODataPer $batch request
Max SuiteTalk page size1,000 recordsNetSuite SuiteTalk RESTDefault 100

Rolling / Daily Limits

Limit TypeValueWindowSystem
API calls100,000 (Enterprise) / 5M (Unlimited)24h rollingSalesforce
Bulk API batches15,00024h rollingSalesforce
Concurrent long-running requests25Per orgSalesforce
Concurrent REST requests5 (default) / 10 (SuiteCloud Plus)Per accountNetSuite
OData requestsFair-use / throttledPer tenantSAP S/4HANA Cloud
Dynamics 365 OData6,000 requests/5 min per user5-min rollingMicrosoft D365
Oracle CPQ concurrent~50 (throttled)Per tenantOracle CPQ

Transaction / Governor Limits (Salesforce)

Limit TypePer-Transaction ValueNotes
SOQL queries100CPQ order generation triggers can cascade
DML statements150Each insert/update/delete = 1
Callouts100HTTP requests to external services
CPU time10,000 ms (sync) / 60,000 ms (async)Complex bundles are the main risk
Heap size6 MB (sync) / 12 MB (async)500+ line quotes can hit this
Future calls50 per transactionLimits parallel async processing

Authentication

SystemFlowUse WhenToken LifetimeNotes
SalesforceOAuth 2.0 JWT BearerServer-to-server middleware2h (session timeout)Recommended; requires connected app + certificate
Oracle CPQOAuth 2.0 Client CredentialsServer-to-server via OIC1h (configurable)Standard for OIC integrations
SAP S/4HANAOAuth 2.0 + x-csrf-tokenAll write operationsToken: 30 min, csrf: per-sessionMust fetch csrf before POST/PATCH
NetSuiteToken-Based Auth (TBA)Server-to-serverDoes not expireRequires integration record + token pair
Dynamics 365OAuth 2.0 via Azure ADAll API accessAccess: 1h, Refresh: 90 daysRegister app in Azure AD

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START — Need to create ERP orders from CPQ quotes
├── Which CPQ platform?
│   ├── Salesforce CPQ (managed package)
│   │   ├── Order volume < 500/day?
│   │   │   ├── YES → SBQQ Ordered checkbox + trigger-based callout to ERP
│   │   │   └── NO → Platform Events + middleware (MuleSoft/Boomi) → ERP
│   │   └── Need real-time order creation?
│   │       ├── YES → Apex trigger on Order → callout to ERP REST API
│   │       └── NO → Scheduled batch: query new Orders → bulk push to ERP
│   ├── Salesforce Revenue Cloud
│   │   ├── Native to Salesforce ecosystem?
│   │   │   ├── YES → Revenue Cloud Order Management → Salesforce Order object
│   │   │   └── NO → Order Management API → middleware → ERP
│   │   └── Need external ERP sync?
│   │       ├── YES → Platform Events on Order creation → middleware → ERP API
│   │       └── NO → Stay within Salesforce ecosystem
│   └── Oracle CPQ Cloud
│       ├── Target ERP is Oracle ERP Cloud / Fusion?
│       │   ├── YES → Native OIC integration (pub/sub) → Order Management
│       │   └── NO ↓
│       ├── Using Oracle Integration Cloud (OIC)?
│       │   ├── YES → CPQ publishes to OIC queue → OIC transforms → ERP API
│       │   └── NO → CPQ Commerce REST → custom middleware → ERP API
│       └── Need real-time?
│           ├── YES → Commerce action triggers → synchronous integration
│           └── NO → Batch export via Bulk Data Services → scheduled import
├── Which target ERP?
│   ├── SAP S/4HANA → OData v4 (A_SalesOrder) or BAPI via RFC
│   ├── Oracle NetSuite → SuiteTalk REST (POST /salesOrder) or RESTlet
│   ├── Microsoft D365 → OData v4 (SalesOrderHeaders data entity)
│   └── Oracle ERP Cloud → REST API (fscmRestApi/resources/salesOrders)
└── Error strategy?
    ├── Zero-loss → idempotent key (quote# + version) + dead letter queue
    └── Best-effort → retry 3x with exponential backoff, then alert

Quick Reference

Salesforce CPQ-to-ERP Order Creation Flow

StepSource SystemActionTarget SystemData ObjectsFailure Handling
1Salesforce CPQQuote approved → Ordered checkbox → Order auto-createdSalesforce (internal)Order, OrderItemGovernor limit check; silent failure if exceeded
2SalesforcePlatform Event on Order creationMiddlewareOrder payload (JSON)Retry with idempotency key
3MiddlewareTransform SF Order → ERP formatTarget ERPMapped sales order + linesTransform errors → dead letter queue
4MiddlewarePOST sales order to ERPSAP / NetSuite / D365Sales order entity429 → backoff; validation → DLQ
5Target ERPReturn order IDMiddleware → SalesforceERP Order NumberWrite-back to SF Order

Oracle CPQ-to-ERP Order Creation Flow

StepSource SystemActionTarget SystemData ObjectsFailure Handling
1Oracle CPQQuote submitted → order_start actionCPQ (internal)Transaction (Commerce doc)BML validation; errors block submission
2Oracle CPQPublishes event to OIC queueOICTransaction payloadOIC error handling + retry
3OIC/MiddlewareTransform → ERP order formatTarget ERPSales order + linesTransform failures → error hospital
4MiddlewarePOST to ERP order APITarget ERPSales orderSame as SF flow
5Target ERPReturn order IDOIC → CPQOrder numberWrite-back via order_update

Step-by-Step Integration Guide

1. Configure Salesforce CPQ Order Generation

Set up CPQ order generation so approved quotes automatically produce Order and Order Product records. [src1, src5]

// Check quote readiness and trigger order generation
SBQQ__Quote__c quote = [
    SELECT Id, SBQQ__Ordered__c, SBQQ__Primary__c, SBQQ__Status__c
    FROM SBQQ__Quote__c WHERE Id = :quoteId
];
System.assert(quote.SBQQ__Primary__c == true, 'Quote must be Primary');
System.assert(quote.SBQQ__Status__c == 'Approved', 'Quote must be Approved');
quote.SBQQ__Ordered__c = true;
update quote;
// CPQ package trigger creates Order + OrderItem records automatically

Verify: SELECT Id, OrderNumber FROM Order WHERE SBQQ__Quote__c = :quoteId → expect 1 Order with Status = 'Draft'.

2. Capture Order Creation Event

Use Platform Event or Apex trigger to detect new CPQ-generated orders and push to middleware. [src1]

// Apex Trigger: Fire Platform Event on CPQ Order creation
trigger OrderCreatedTrigger on Order (after insert) {
    List<CPQ_Order_Event__e> events = new List<CPQ_Order_Event__e>();
    for (Order ord : Trigger.new) {
        if (ord.SBQQ__Quote__c != null) {
            events.add(new CPQ_Order_Event__e(
                Order_Id__c = ord.Id,
                Quote_Id__c = ord.SBQQ__Quote__c,
                Account_Id__c = ord.AccountId
            ));
        }
    }
    if (!events.isEmpty()) { EventBus.publish(events); }
}

Verify: Platform Event subscription receives event with correct Order_Id__c.

3. Transform and POST to ERP

Map Salesforce Order to target ERP schema, then create sales order. [src4, src6]

# SAP S/4HANA: Create sales order via OData v4
# Step 1: Fetch CSRF token
curl -X GET "${SAP_HOST}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder" \
  -H "Authorization: Bearer ${SAP_TOKEN}" -H "x-csrf-token: fetch" -D -

# Step 2: POST sales order with idempotency key
curl -X POST "${SAP_HOST}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder" \
  -H "Authorization: Bearer ${SAP_TOKEN}" \
  -H "x-csrf-token: ${CSRF_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"SalesOrderType":"OR","SoldToParty":"CUST001",
       "PurchaseOrderByCustomer":"SF-Q-00123-v1",
       "to_Item":{"results":[{"Material":"MAT-001","RequestedQuantity":"5"}]}}'

Verify: HTTP 201 with SalesOrder number in response body.

4. Write ERP Order Number Back to Salesforce

Update Salesforce Order with ERP order number for cross-reference and audit trail. [src6]

// Write-back ERP order number to Salesforce
await sfClient.sobject('Order').update({
  Id: sfOrderId,
  ERP_Order_Number__c: erpOrderNumber,
  ERP_Sync_Status__c: 'Synced',
  ERP_Sync_Timestamp__c: new Date().toISOString()
});

Verify: SELECT ERP_Order_Number__c FROM Order WHERE Id = :sfOrderId returns ERP order number.

5. Handle Oracle CPQ-to-ERP via OIC

Use Commerce REST API's order actions, then route through Oracle Integration Cloud. [src2, src3]

# Oracle CPQ: Trigger order processing on approved transaction
curl -X POST \
  "https://${CPQ_INSTANCE}.oracle.com/rest/v19/commerce/${PROCESS}/${MAIN_DOC}/${TXN_ID}/actions/order_start" \
  -H "Authorization: Bearer ${ORACLE_CPQ_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"comments":"Order initiated from approved quote"}'

Verify: Transaction status changes; OIC dashboard shows integration flow execution.

Code Examples

Python: Salesforce CPQ Order to NetSuite Sales Order

# Input:  Salesforce Order ID (from CPQ Ordered checkbox)
# Output: NetSuite Sales Order internal ID

from simple_salesforce import Salesforce  # v1.12+
import requests

def sync_cpq_order_to_netsuite(sf_order_id, sf, ns_url, ns_headers):
    order = sf.query(f"""
        SELECT Id, OrderNumber, Account.NetSuite_Internal_ID__c,
               (SELECT Product2.NetSuite_Item_ID__c, Quantity,
                       SBQQ__QuoteLine__r.SBQQ__NetPrice__c
                FROM OrderItems)
        FROM Order WHERE Id = '{sf_order_id}'
    """)['records'][0]

    ns_order = {
        'entity': {'id': order['Account']['NetSuite_Internal_ID__c']},
        'otherRefNum': order['OrderNumber'],  # Idempotency key
        'item': {'items': [{
            'item': {'id': li['Product2']['NetSuite_Item_ID__c']},
            'quantity': li['Quantity'],
            'rate': str(li['SBQQ__QuoteLine__r']['SBQQ__NetPrice__c'])
        } for li in order['OrderItems']['records']]}
    }

    resp = requests.post(ns_url + '/salesOrder', json=ns_order, headers=ns_headers)
    if resp.status_code == 204:
        return resp.headers.get('Location', '').split('/')[-1]
    raise Exception(f'NetSuite error {resp.status_code}: {resp.text}')

JavaScript/Node.js: Salesforce CPQ Order to Dynamics 365

// Input:  Salesforce Order payload (from Platform Event)
// Output: D365 Sales Order entity ID

const axios = require('axios');  // v1.6+

async function createD365SalesOrder(sfOrder, d365Config) {
  const tokenResp = await axios.post(
    `https://login.microsoftonline.com/${d365Config.tenantId}/oauth2/v2.0/token`,
    new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: d365Config.clientId,
      client_secret: d365Config.clientSecret,
      scope: `${d365Config.resourceUrl}/.default`
    })
  );

  const d365Order = {
    SalesOrderNumber: sfOrder.OrderNumber,
    OrderingCustomerAccountNumber: sfOrder.Account.D365_Customer_Number__c,
    SalesOrderLines: sfOrder.OrderItems.records.map((item, idx) => ({
      ItemNumber: item.Product2.D365_Item_Number__c,
      SalesQuantity: item.Quantity,
      SalesPrice: item.UnitPrice
    }))
  };

  const resp = await axios.post(
    `${d365Config.resourceUrl}/data/SalesOrderHeadersV2`,
    d365Order,
    { headers: { 'Authorization': `Bearer ${tokenResp.data.access_token}` } }
  );
  return resp.data.SalesOrderNumber;
}

cURL: Test Oracle CPQ Transaction Retrieval

# Input:  Oracle CPQ token, transaction ID
# Output: Transaction with line items

curl -X GET \
  "https://${CPQ_INSTANCE}.oracle.com/rest/v19/commerce/${PROCESS}/${MAIN_DOC}/${TXN_ID}?expand=_lineItems" \
  -H "Authorization: Bearer ${ORACLE_CPQ_TOKEN}" \
  -H "Accept: application/json" | jq '{
    transactionId: ._id, status: ._status,
    totalPrice: .totalPrice_t, currency: .currency_t,
    lineItems: [._lineItems[]? | {partNumber: .partNumber_l,
      quantity: .quantity_l, netPrice: .netPrice_l}]
  }'

Data Mapping

Field Mapping Reference: Salesforce CPQ to ERP

CPQ Source FieldSAP S/4HANANetSuiteD365TypeGotcha
Order.OrderNumberPurchaseOrderByCustomerotherRefNumSalesOrderNumberStringIdempotency key — must be unique in ERP
Account.ERP_Customer_ID__cSoldToPartyentity.idOrderingCustomerAccountNumberStringNetSuite requires internalId, not externalId
OrderItem.Product2.ERP_Material_ID__cMaterialitem.idItemNumberStringSAP pads to 18 chars; NetSuite uses internalId
OrderItem.QuantityRequestedQuantityquantitySalesQuantityDecimalSAP stores as string
OrderItem.UnitPriceNetPriceAmountrateSalesPriceCurrencyVerify currency match across systems
SBQQ__QuoteLine__r.SBQQ__Discount__cConditionType ZDISCdiscountRateDiscountPercentage%SAP uses condition types
Order.CurrencyIsoCodeTransactionCurrencycurrencyRecord.refNameCurrencyCodeStringNetSuite may need currency internalId
OrderItem.ServiceDateRequestedDeliveryDateexpectedShipDateRequestedShipDateDateSAP internal: YYYYMMDD
SBQQ__Quote__c.SBQQ__PaymentTerms__cPaymentTermsterms.refNamePaymentTermsNameStringERP codes differ from CPQ picklist values

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeSystemMeaningResolution
429SF, NetSuite, D365Rate limit exceededExponential backoff; check Retry-After header
INVALID_FIELDSalesforceField not writableVerify SBQQ__ namespace; check FLS
UNABLE_TO_LOCK_ROWSalesforceRecord lockedRetry with random jitter (500-2000ms)
403 + csrf errorSAP S/4HANACSRF token missing/expiredFetch new token before retry
DUPLICATE_VALUENetSuiteDuplicate external IDLog as success — order already exists
SSS_REQUEST_LIMIT_EXCEEDEDNetSuiteGovernance limitWait for next window; optimize scripts
INVALID_SESSION_IDSF, Oracle CPQSession expiredRefresh token and retry

Failure Points in Production

Anti-Patterns

Wrong: Polling Salesforce for new orders every 60 seconds

// BAD — wastes API calls, high latency, misses rapid order creation
setInterval(async () => {
  const newOrders = await sf.query(
    "SELECT Id FROM Order WHERE CreatedDate > LAST_N_MINUTES:1"
  );
  for (const order of newOrders.records) { await pushToERP(order.Id); }
}, 60000);

Correct: Use Platform Events for real-time, event-driven sync

// GOOD — real-time, no polling, no wasted API calls
const faye = require('faye');
const client = new faye.Client(sf.instanceUrl + '/cometd/66.0');
client.subscribe('/event/CPQ_Order_Event__e', async (message) => {
  await pushToERP(message.data.payload.Order_Id__c);
});

Wrong: Creating ERP order without idempotency key

# BAD — retries create duplicate orders
def create_order(data):
    return requests.post(erp_url + '/salesOrder', json=data).json()

Correct: Use CPQ order number as idempotency key

# GOOD — check for existing before creating
def create_order(data, sf_order_num):
    existing = requests.get(erp_url + f'/salesOrder?q=ref IS "{sf_order_num}"')
    if existing.json().get('count', 0) > 0:
        return existing.json()['items'][0]  # Already exists
    data['otherRefNum'] = sf_order_num
    return requests.post(erp_url + '/salesOrder', json=data).json()

Wrong: Synchronous callout inside Salesforce trigger

// BAD — blocks trigger, governor limits, mixed DML
trigger OrderTrigger on Order (after insert) {
    for (Order ord : Trigger.new) {
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://erp.example.com/api/order');
        req.setMethod('POST');
        h.send(req);  // Synchronous callout in trigger = BAD
    }
}

Correct: Use Queueable for async callouts

// GOOD — async processing, respects governor limits
trigger OrderTrigger on Order (after insert) {
    Set<Id> orderIds = new Set<Id>();
    for (Order ord : Trigger.new) {
        if (ord.SBQQ__Quote__c != null) orderIds.add(ord.Id);
    }
    if (!orderIds.isEmpty())
        System.enqueueJob(new ERPOrderSyncQueueable(orderIds));
}

Common Pitfalls

Diagnostic Commands

# Check if CPQ order was generated from quote (Salesforce)
curl -s "${SF_INSTANCE}/services/data/v66.0/query?q=SELECT+Id,OrderNumber,Status+FROM+Order+WHERE+SBQQ__Quote__c='${QUOTE_ID}'" \
  -H "Authorization: Bearer ${SF_TOKEN}" | jq '.records'

# Check Salesforce API usage / remaining limits
curl -s "${SF_INSTANCE}/services/data/v66.0/limits" \
  -H "Authorization: Bearer ${SF_TOKEN}" | jq '{DailyApiRequests}'

# Check Oracle CPQ transaction status
curl -s "https://${CPQ_INSTANCE}.oracle.com/rest/v19/commerce/${PROCESS}/${MAIN_DOC}/${TXN_ID}" \
  -H "Authorization: Bearer ${ORACLE_CPQ_TOKEN}" | jq '{status: ._status, total: .totalPrice_t}'

# Search SAP for order by PO number (SF order number)
curl -s "${SAP_HOST}/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?\$filter=PurchaseOrderByCustomer%20eq%20'${SF_ORDER_NUMBER}'" \
  -H "Authorization: Bearer ${SAP_TOKEN}" | jq '.d.results'

# Search NetSuite for order by external reference
curl -s "https://${NS_ACCOUNT}.suitetalk.api.netsuite.com/services/rest/record/v1/salesOrder?q=otherRefNum%20IS%20%22${SF_ORDER_NUMBER}%22" \
  -H "Authorization: Bearer ${NS_TOKEN}" | jq '.items'

# Check D365 sales order
curl -s "${D365_URL}/data/SalesOrderHeadersV2?\$filter=SalesOrderNumber%20eq%20'${SF_ORDER_NUMBER}'" \
  -H "Authorization: Bearer ${D365_TOKEN}" | jq '.value'

Version History & Compatibility

ComponentVersionRelease DateStatusKey Changes
Salesforce CPQ (managed package)v66.0 (Spring '26)2026-02Current (sunset announced)Last major release; Revenue Cloud migration tools
Salesforce Revenue CloudSpring '262026-02Current (GA)Order Management API v2; multi-currency
Oracle CPQ REST APIv19 (24D)2025-12CurrentBulk Data Services; enhanced Commerce actions
SAP S/4HANA API24082024-08CurrentAsync OData; enhanced A_SalesOrder
NetSuite SuiteTalk REST2024.22024-09CurrentEnhanced SuiteQL for complex queries
D365 F&SCM10.0.392025-04CurrentSalesOrderHeadersV2 improvements

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
B2B with complex product configuration and approval workflowsSimple B2C eCommerce cart ordersbusiness/erp-integration/ecommerce-to-erp/2026
Multi-line quotes with bundles and negotiated pricingSingle-product fixed-price ordersDirect ERP order entry or simple webhook
CPQ approvals and discount governance must be preserved in ERPNo pricing complexity — ERP prices are authoritativeSkip CPQ; create orders directly in ERP
Need bidirectional status sync (order → fulfillment → CPQ)One-way push without status trackingSimple REST POST
Multi-currency, multi-entity ordersSingle-currency, single-entitySimplified integration without FX handling

Cross-System Comparison

CapabilitySalesforce CPQSalesforce Revenue CloudOracle CPQ CloudNotes
Order generation triggerOrdered checkbox on quoteOrder Management APICommerce action (order_start)SF CPQ is declarative; Oracle is API-driven
API styleREST (sObject CRUD)REST (Order Mgmt API)Commerce REST API v19Revenue Cloud API is newer
Pricing engineApex (managed package)Salesforce nativeServer-side BMLCPQ pricing must match ERP pricing
Native ERP integrationNone (middleware required)Salesforce-native onlyOracle ERP Cloud via OICOracle CPQ + Oracle ERP = strongest native
Middleware optionsMuleSoft, Boomi, Workato, CeligoSame + native connectorsOIC, MuleSoft, BoomiOIC recommended for Oracle CPQ
Rate limit100K API calls/24h (Enterprise)Shared with REST APIThrottled (~50 concurrent)SF has most transparent limits
Bulk order supportBulk API 2.0Bulk API 2.0Bulk Data Services (24D+)All support high volume
Sunset riskSunset Mar 2025 / Aug 2026Active (successor)ActiveSF CPQ customers must plan migration
Multi-currencySalesforce MCENativeBuilt-in engineAll support multi-currency
Approval workflowSF Approvals + CPQ AdvancedFlow-basedCPQ workflow engineAll have approval capabilities

Important Caveats

Related Units