BLK-SHIRT-L in Shopify vs BLACK_SHIRT_LARGE in your ERP will silently break sync. [src3, src7]This integration playbook covers connecting the three dominant ecommerce platforms -- Shopify, Adobe Commerce (Magento 2), and BigCommerce -- to major ERP systems for automated order processing and inventory synchronization. The primary data flows are: orders from ecommerce to ERP (including customer data, line items, payment status, and shipping addresses), and inventory/fulfillment data from ERP back to ecommerce (stock levels, tracking numbers, shipment status). [src2, src6]
The playbook focuses on cloud-to-cloud integrations using native APIs and iPaaS middleware. On-premise Magento installations follow the same API patterns but require network/firewall configuration not covered here. For direct CRM-to-ERP integration (e.g., Salesforce opportunity to NetSuite sales order without an ecommerce frontend), see the Order-to-Cash Integration card instead. [src3]
| System | Role | API Surface | Direction |
|---|---|---|---|
| Shopify | Ecommerce storefront -- order capture | REST Admin, GraphQL Admin, Webhooks | Outbound (orders) / Inbound (inventory) |
| Adobe Commerce (Magento 2) | Ecommerce storefront -- order capture | REST, GraphQL, Message Queues | Outbound (orders) / Inbound (inventory) |
| BigCommerce | Ecommerce storefront -- order capture | REST V2/V3, Webhooks | Outbound (orders) / Inbound (inventory) |
| Oracle NetSuite | ERP -- financial master, fulfillment | SuiteTalk SOAP, REST API, RESTlets | Inbound (orders) / Outbound (inventory, fulfillment) |
| SAP S/4HANA | ERP -- financial master, fulfillment | OData v4, IDoc | Inbound (orders) / Outbound (inventory, fulfillment) |
| D365 Business Central | ERP -- financial master, fulfillment | OData v4, REST API v2.0 | Inbound (orders) / Outbound (inventory, fulfillment) |
| iPaaS Middleware | Orchestrator -- data transformation | Celigo, Boomi, MuleSoft, Workato | Bidirectional |
| API Surface | Platform | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|---|
| GraphQL Admin API | Shopify | HTTPS/JSON | Orders, products, inventory (recommended) | 250 items per input | 100-1,000 pts/s by plan | Yes | Via Bulk Operations |
| REST Admin API | Shopify | HTTPS/JSON | Legacy, simple CRUD | 250 per page | 2 req/s (40 bucket) | Yes | No |
| Webhooks | Shopify | HTTPS/JSON | Event-driven order capture | 1 event per call | N/A (push) | Yes | No |
| REST API | Magento 2 | HTTPS/JSON | Orders, products, inventory | 300 per page | Disabled by default | Yes | Via async endpoints |
| Async Bulk API | Magento 2 | HTTPS/JSON | High-volume inventory updates | 5,000 per request | Configurable via Redis | No | Yes |
| REST V3 API | BigCommerce | HTTPS/JSON | Orders, products, catalog | 250 per page | 150-450 req/30s | Yes | No |
| Webhooks | BigCommerce | HTTPS/JSON | Event-driven order capture | 1 event per call | N/A (push) | Yes | No |
| SuiteTalk SOAP | NetSuite | HTTPS/XML | Order creation, inventory reads | 1,000 per search | 5 concurrent (default) | Yes | Via CSV import |
| OData v4 | SAP S/4HANA | HTTPS/JSON | Sales orders, inventory | 5,000 per page | Throttled/fair-use | Yes | Via $batch |
| REST API v2.0 | D365 BC | HTTPS/JSON | Sales orders, items, inventory | 20,000 per page | Throttled/fair-use | Yes | Via $batch |
| Limit Type | Value | Platform | Notes |
|---|---|---|---|
| Max items per GraphQL mutation | 250 | Shopify | Use bulkOperationRunMutation for larger batches |
| Max query cost per request | 1,000 points | Shopify GraphQL | Single query cannot exceed this regardless of plan |
| Max REST response page size | 250 records | Shopify REST | Use page_info cursor pagination |
| Max records per search | 1,000 | NetSuite SuiteTalk | Use searchMoreWithId for pagination |
| Max $top per OData request | 5,000 | SAP S/4HANA | Server-driven paging for larger sets |
| Max request body | 100 MB | Magento 2 Async | For bulk async operations |
| Max webhook response time | 19 seconds | Shopify | Exceed = timeout + retry queue |
| Limit Type | Value | Window | Platform Differences |
|---|---|---|---|
| Shopify GraphQL points | 100-1,000/s | Per second (leaky bucket) | Standard: 100, Advanced: 200, Plus: 1,000, Enterprise: 2,000 |
| Shopify REST requests | 40 bucket (2/s refill) | Rolling | Shared across all apps on store |
| BigCommerce API requests | 150-450 / 30s | 30-second window | Standard/Plus: 150, Pro: 450, Enterprise: custom |
| BigCommerce webhooks | 60 / min | Per minute | Across all webhook consumers |
| NetSuite concurrent requests | 5-10 | Per account | Default 5; SuiteCloud Plus: 10+ |
| NetSuite governance units | 1,000 (scheduled) / 10,000 (map-reduce) | Per script execution | SuiteScript 2.x governance |
| Platform | Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|---|
| Shopify | OAuth 2.0 | Public/custom apps | Permanent (offline) | No | Offline access tokens don't expire |
| Shopify | Custom App Token | Private integrations | Permanent | No | Generated in Shopify Admin |
| Magento 2 | OAuth 1.0a | Third-party integrations | Permanent (until revoked) | No | 4-step handshake required |
| Magento 2 | Bearer Token | Admin/customer API | 4 hours (admin) | No | POST /rest/V1/integration/admin/token |
| BigCommerce | OAuth + API Token | All integrations | Permanent | No | Store-level, scoped permissions |
| NetSuite | Token-Based Auth (TBA) | Server-to-server | Permanent (until revoked) | No | Recommended over credentials |
| SAP S/4HANA | OAuth 2.0 Client Credentials | Server-to-server | Configurable (default 12h) | Yes | Requires x-csrf-token for writes |
| D365 BC | OAuth 2.0 (Azure AD) | Server-to-server | 1 hour | Yes | Azure App Registration required |
GET with x-csrf-token: fetch header before every POST/PATCH/DELETE. [src6]SHOP-1001, BC-1001) as external IDs in ERP. [src3, src7]START -- Ecommerce-to-ERP integration
|-- What data flow?
| |-- Orders (ecommerce to ERP)
| | |-- Volume < 100 orders/day?
| | | |-- YES: Webhook-driven (near-real-time, simplest)
| | | +-- NO:
| | |-- Volume 100-1,000 orders/day?
| | | |-- YES: Webhook + queue (RabbitMQ/SQS) then ERP batch insert
| | | +-- NO:
| | |-- Volume > 1,000 orders/day?
| | | |-- YES: Webhook + queue + iPaaS batch processing (every 5-15min)
| | | +-- Flash sale / > 10K/day? iPaaS with auto-scaling + ERP bulk import
| | +-- Need immediate ERP confirmation?
| | |-- YES: Synchronous API call (watch rate limits!)
| | +-- NO: Async webhook + queue + batch (recommended)
| |-- Inventory (ERP to ecommerce)
| | |-- SKU count < 1,000? Scheduled poll every 5-15min
| | |-- SKU count < 50,000? Delta sync (changed-since) every 5-15min
| | +-- Need < 1min latency? ERP-side CDC/webhook push
| |-- Fulfillment/Tracking (ERP to ecommerce)
| | +-- Always push-based: ERP triggers update via middleware
| +-- Product/Catalog (ERP to ecommerce)
| +-- Scheduled batch sync (hourly/daily)
|-- Which middleware?
| |-- < 100 orders/day: Direct API (no middleware needed)
| |-- 100-1,000/day: Celigo, Workato, or native connector
| |-- > 1,000/day: MuleSoft, Boomi, or Celigo scaling tier
| +-- Multi-channel: iPaaS required
+-- Error handling?
|-- Zero-loss: Idempotent writes + DLQ + reconciliation job
+-- Best-effort: Retry with exponential backoff (max 5 retries)
| Step | Source System | Action | Target System | Data Objects | Failure Handling |
|---|---|---|---|---|---|
| 1 | Shopify/Magento/BC | Order placed -- webhook fires | iPaaS Queue | Order header, line items, customer, shipping | Webhook retries (Shopify: 19x/48h) |
| 2 | iPaaS | Transform + validate order data | ERP | Sales Order / Sales Invoice | Retry 3x with backoff, then DLQ |
| 3 | iPaaS | Create/match customer record | ERP | Customer / Business Partner | Match on email; create if not found |
| 4 | iPaaS | Map SKUs to ERP item IDs | ERP | Item / Material / Product | Fail order if SKU not found (alert) |
| 5 | ERP | Allocate inventory, create pick/pack | WMS / 3PL | Fulfillment record | Manual review queue |
| 6 | ERP | Ship order, generate tracking | iPaaS | Shipment + tracking number | Retry push to ecommerce |
| 7 | iPaaS | Update order with tracking | Shopify/Magento/BC | Fulfillment / Shipment record | Retry 3x, then alert |
| 8 | ERP | Adjust inventory quantities | iPaaS | Inventory levels per location | Delta sync on schedule |
| 9 | iPaaS | Push inventory updates | Shopify/Magento/BC | Inventory quantities per SKU | Retry, then full reconciliation |
| 10 | ERP | Invoice created, payment matched | Accounting | Invoice + payment | Manual reconciliation |
Register webhooks on your ecommerce platform to receive order events. This eliminates polling and ensures near-real-time order flow. [src1, src4]
Shopify (GraphQL Admin API):
mutation {
webhookSubscriptionCreate(
topic: ORDERS_CREATE
webhookSubscription: {
callbackUrl: "https://your-middleware.com/webhooks/shopify/orders"
format: JSON
}
) {
webhookSubscription { id }
userErrors { field message }
}
}
BigCommerce (REST V3):
curl -X POST https://api.bigcommerce.com/stores/{store_hash}/v3/hooks \
-H "X-Auth-Token: {api_token}" \
-H "Content-Type: application/json" \
-d '{"scope": "store/order/created", "destination": "https://your-middleware.com/webhooks/bigcommerce/orders", "is_active": true}'
Verify: Shopify Admin > Settings > Notifications > Webhooks shows new subscription; BigCommerce: GET /v3/hooks returns active hook
Never process orders synchronously in the webhook handler. Acknowledge immediately, then queue for async processing. [src3, src7]
# Python (Flask) -- webhook receiver with queue
import hmac, hashlib, json, base64
from flask import Flask, request, jsonify
import boto3
app = Flask(__name__)
sqs = boto3.client('sqs')
QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789/ecommerce-orders"
@app.route('/webhooks/shopify/orders', methods=['POST'])
def shopify_order_webhook():
# Verify HMAC signature (critical for security)
hmac_header = request.headers.get('X-Shopify-Hmac-Sha256', '')
digest = hmac.new(b'YOUR_WEBHOOK_SECRET', request.data, hashlib.sha256).digest()
computed = base64.b64encode(digest).decode()
if not hmac.compare_digest(computed, hmac_header):
return jsonify({"error": "Invalid signature"}), 401
# Queue immediately, respond fast (< 5s)
order = json.loads(request.data)
sqs.send_message(QueueUrl=QUEUE_URL, MessageBody=json.dumps({
"platform": "shopify", "order_id": order["id"], "payload": order
}), MessageGroupId="orders")
return jsonify({"status": "queued"}), 200
Verify: Send a test order → check queue depth increases by 1
Map ecommerce order fields to ERP-specific sales order format. This is where most integrations break. [src3, src7]
# Python -- Shopify order to NetSuite Sales Order mapping
def map_shopify_to_netsuite(shopify_order):
customer = {
"email": shopify_order["email"],
"firstName": shopify_order["customer"]["first_name"],
"lastName": shopify_order["customer"]["last_name"],
"externalId": f"SHOP-{shopify_order['customer']['id']}"
}
items = []
for item in shopify_order["line_items"]:
items.append({
"item": {"externalId": item["sku"]},
"quantity": item["quantity"],
"rate": float(item["price"]),
})
sales_order = {
"externalId": f"SHOP-{shopify_order['order_number']}",
"entity": {"externalId": f"SHOP-{shopify_order['customer']['id']}"},
"tranDate": shopify_order["created_at"][:10],
"itemList": {"item": items},
}
return customer, sales_order
Verify: Run mapping on a sample order → validate all required ERP fields populated, no nulls in mandatory fields
Use external IDs to make order creation idempotent -- retrying the same order must not create duplicates. [src3]
# Python -- NetSuite REST API order creation with idempotency
def create_netsuite_order(sales_order, auth_headers):
response = requests.post(NETSUITE_URL, json=sales_order, headers={
**auth_headers, "Content-Type": "application/json",
"Prefer": "return=representation"
})
if response.status_code == 204:
return {"status": "created", "id": response.headers.get("Location")}
elif response.status_code == 409:
return {"status": "already_exists"} # Idempotent success
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
raise RateLimitError(f"Retry after {retry_after}s")
else:
raise IntegrationError(f"Error {response.status_code}: {response.text}")
Verify: GET /services/rest/record/v1/salesOrder?q=externalId IS "SHOP-1001" returns the created order
Pull inventory changes from ERP since last sync, then push updates to ecommerce platform. Always ERP to ecommerce direction. [src2, src3]
# Python -- NetSuite to Shopify inventory sync (delta)
def sync_inventory_netsuite_to_shopify(last_sync_time):
suiteql = f"""SELECT item.itemId AS sku, il.quantityAvailable AS qty
FROM inventoryBalance il JOIN item ON il.item = item.id
WHERE item.lastModifiedDate > '{last_sync_time}'"""
changed_items = netsuite_query(suiteql)
for item in changed_items:
shopify_graphql("inventorySetQuantities", {
"name": "available", "reason": "correction",
"quantities": [{"inventoryItemId": sku_to_id(item["sku"]),
"locationId": LOCATION_ID, "quantity": int(item["qty"])}]
})
Verify: Change quantity in NetSuite → run sync → Shopify Admin shows updated stock
When the ERP ships an order, push tracking information back to the ecommerce platform. [src3]
// Node.js -- Push NetSuite fulfillment to Shopify
async function pushFulfillmentToShopify(orderId, trackingNumber, carrier) {
const shopifyOrderId = orderId.replace('SHOP-', '');
const foResponse = await axios.get(
`https://${SHOP}.myshopify.com/admin/api/2025-01/orders/${shopifyOrderId}/fulfillment_orders.json`,
{ headers: { 'X-Shopify-Access-Token': ACCESS_TOKEN } }
);
const fulfillmentOrderId = foResponse.data.fulfillment_orders[0].id;
await axios.post(
`https://${SHOP}.myshopify.com/admin/api/2025-01/fulfillments.json`,
{ fulfillment: {
line_items_by_fulfillment_order: [{ fulfillment_order_id: fulfillmentOrderId }],
tracking_info: { number: trackingNumber, company: carrier },
notify_customer: true
}},
{ headers: { 'X-Shopify-Access-Token': ACCESS_TOKEN } }
);
}
Verify: GET /admin/api/2025-01/orders/{id}/fulfillments.json shows status: success with tracking_number
# Input: Dict of SKU to quantity mappings from ERP export
# Output: Shopify inventory levels updated for all changed SKUs
import requests, json, time
SHOP = "your-store.myshopify.com"
TOKEN = "shpat_xxxxx"
HEADERS = {"X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"}
API_URL = f"https://{SHOP}/admin/api/2025-01/graphql.json"
def bulk_inventory_update(sku_qty_map, location_id):
# Build JSONL for bulk mutation
jsonl_lines = [json.dumps({"input": {"name": "available", "reason": "correction",
"quantities": [{"inventoryItemId": sku_to_id(sku),
"locationId": f"gid://shopify/Location/{location_id}",
"quantity": qty}]}}) for sku, qty in sku_qty_map.items()]
# Stage upload, then run bulk mutation
# ... (see full code in .md file)
// Input: BigCommerce store credentials + date range
// Output: Array of order objects for ERP transformation
async function fetchOrdersWithRateLimit(sinceDate) {
let allOrders = [], page = 1, hasMore = true;
while (hasMore) {
try {
const response = await axios.get(`${BC_API}/orders`, {
headers: HEADERS,
params: { min_date_created: sinceDate, limit: 250, page }
});
allOrders = allOrders.concat(response.data.data);
const remaining = parseInt(response.headers['x-rate-limit-requests-left'] || '100');
if (remaining < 10) {
const resetMs = parseInt(response.headers['x-rate-limit-time-reset-ms'] || '5000');
await new Promise(r => setTimeout(r, resetMs));
}
hasMore = response.data.data.length === 250;
page++;
} catch (err) {
if (err.response?.status === 429) {
const resetMs = parseInt(err.response.headers['x-rate-limit-time-reset-ms'] || '15000');
await new Promise(r => setTimeout(r, resetMs));
} else throw err;
}
}
return allOrders;
}
# Shopify: Test auth + fetch recent orders
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2025-01/orders.json?limit=5&status=any" \
-H "X-Shopify-Access-Token: shpat_XXXXX" | jq '.orders | length'
# BigCommerce: Test auth + fetch orders
curl -s "https://api.bigcommerce.com/stores/STORE_HASH/v3/orders?limit=5" \
-H "X-Auth-Token: YOUR_TOKEN" | jq '.data | length'
# Magento 2: Get bearer token + fetch orders
TOKEN=$(curl -s -X POST "https://your-magento.com/rest/V1/integration/admin/token" \
-H "Content-Type: application/json" -d '{"username":"admin","password":"pass"}' | tr -d '"')
curl -s "https://your-magento.com/rest/V1/orders?searchCriteria[pageSize]=5" \
-H "Authorization: Bearer $TOKEN" | jq '.items | length'
| Source Field (Ecommerce) | NetSuite Target | SAP S/4HANA Target | D365 BC Target | Type | Gotcha |
|---|---|---|---|---|---|
| order.id | salesOrder.externalId | SalesOrder.PurchaseOrderByCustomer | salesOrder.externalDocumentNumber | String | Prefix with platform code (SHOP-, BC-, MAG-) |
| order.order_number | salesOrder.otherRefNum | SalesOrder.CustomerPurchaseOrderNumber | salesOrder.number | String | Shopify order_number != id |
| order.created_at | salesOrder.tranDate | SalesOrder.SalesOrderDate | salesOrder.orderDate | Date | Convert timezone: Shopify=UTC, NetSuite=user pref |
| order.total_price | salesOrder.total | SalesOrder.TotalNetAmount | salesOrder.totalAmountIncludingTax | Decimal | Verify 2 vs 4 decimal places |
| order.currency | salesOrder.currency.externalId | SalesOrder.TransactionCurrency | salesOrder.currencyCode | String | NetSuite uses internal IDs, not ISO codes by default |
| line_items[].sku | item.externalId | SalesOrderItem.Material | salesLine.itemNo | String | THE #1 FAILURE POINT -- must match exactly |
| line_items[].quantity | item.quantity | SalesOrderItem.RequestedQuantity | salesLine.quantity | Integer | Check UOM mapping (each vs case) |
| line_items[].price | item.rate | SalesOrderItem.NetAmount | salesLine.unitPrice | Decimal | Tax-inclusive (Shopify EU) vs tax-exclusive (ERPs) |
| customer.email | customer.email | BusinessPartner.EmailAddress | customer.email | String | Primary match key for deduplication |
| shipping_address.zip | shippingAddress.zip | ShipToParty.PostalCode | shipToAddress.postalCode | String | International formats vary (UK: SW1A 1AA) |
| Code | Platform | Meaning | Cause | Resolution |
|---|---|---|---|---|
| 429 | All | Rate limit exceeded | Too many API calls in window | Exponential backoff: wait 2^n * 1s, max 5 retries. Read Retry-After header |
| 422 | Shopify | Unprocessable Entity | Invalid data (missing required field) | Check response body for field-level errors; fix data mapping |
| 401 | All | Unauthorized | Expired or invalid token | Re-authenticate; check app isn't uninstalled |
| 409 | NetSuite | Conflict / Duplicate | externalId already exists | Idempotent -- this is success for retry scenarios |
| 400 | Magento | Bad Request | Invalid search criteria or missing queue | Verify searchCriteria syntax; check RabbitMQ running |
| RCRD_DSNT_EXIST | NetSuite | Record not found | Referenced item/customer doesn't exist | Verify SKU mapping; create missing master data first |
Implement a SKU validation step before order creation -- reject with alert if any SKU missing. [src3]Store processed webhook IDs (X-Shopify-Webhook-Id) in Redis with 48h TTL; skip if already processed. [src1]Catch 401 errors, trigger re-auth flow, queue failed orders for retry. [src2]Map ERP partial shipments to partial fulfillments; track state per line item. [src3]Run full inventory reconciliation job weekly -- compare all quantities and correct mismatches. [src6]Use message queue as buffer; auto-scale consumers; implement circuit breaker on ERP API calls. [src3]# BAD -- wastes API quota, adds latency, hits rate limits at scale
while True:
orders = shopify.get("/orders.json?status=any&created_at_min=" + last_check)
for order in orders:
create_erp_order(order)
time.sleep(60)
# GOOD -- event-driven, near-real-time, respects rate limits
@app.route('/webhooks/shopify/orders', methods=['POST'])
def handle_order_webhook():
verify_hmac(request)
queue.enqueue(request.data) # Queue immediately
return '', 200 # Respond fast
# Queue consumer processes orders at sustainable rate
# BAD -- creates infinite loops and ghost stock
def sync_inventory():
shopify_qty = get_shopify_inventory(sku)
netsuite_qty = get_netsuite_inventory(sku)
if shopify_qty != netsuite_qty:
update_netsuite(sku, shopify_qty) # Wrong! Shopify is not source of truth
update_shopify(sku, netsuite_qty) # Overwrites what you just pushed
# GOOD -- ERP is single source of truth for inventory
def sync_inventory():
changed_items = query_netsuite_changed_since(last_sync_time)
for item in changed_items:
update_shopify_inventory(sku=item["sku"], quantity=item["quantityAvailable"])
save_last_sync_time(datetime.utcnow())
# BAD -- breaks every time a new product is added
SKU_MAP = {"BLK-SHIRT-L": "10042", "WHT-SHIRT-M": "10043"} # 2000 more...
# GOOD -- SKU matching via ERP external ID field
def get_erp_item_id(sku):
result = netsuite_search("item", {"externalId": sku})
if not result:
raise SKUNotFoundError(f"SKU '{sku}' not found in ERP")
return result["internalId"]
BLK-SHIRT-L, ERP uses BLACK_SHIRT_LARGE. Fix: Create a SKU mapping table maintained in iPaaS or shared database. Validate all SKUs exist before processing orders. [src3, src7]Always verify X-Shopify-Hmac-Sha256 header. BigCommerce doesn't sign webhooks -- use IP allowlisting. [src1]Subscribe to orders/updated webhook in addition to orders/create. Implement order amendment logic in ERP. [src2]Load-test against representative environment. Simulate peak traffic before go-live. [src1]Decide one source of truth for tax. Either pass Shopify tax as override, or accept small rounding differences. [src3]After max retries, move to DLQ with original payload + error details. Build admin dashboard for review. [src7]Use competing consumers pattern -- multiple workers from same queue. Scale based on queue depth. [src6]# Shopify: Check API usage / remaining limits
curl -sI "https://YOUR-STORE.myshopify.com/admin/api/2025-01/shop.json" \
-H "X-Shopify-Access-Token: TOKEN" | grep -i "x-shopify"
# Look for: X-Shopify-Shop-Api-Call-Limit: 2/40
# BigCommerce: Check rate limit headers
curl -sI "https://api.bigcommerce.com/stores/HASH/v3/catalog/products?limit=1" \
-H "X-Auth-Token: TOKEN" | grep -i "x-rate-limit"
# Shopify: Verify webhook registrations
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2025-01/webhooks.json" \
-H "X-Shopify-Access-Token: TOKEN" | jq '.webhooks[] | {id, topic, address}'
# Magento: Check integration token validity
curl -s "https://your-magento.com/rest/V1/store/storeConfigs" \
-H "Authorization: Bearer YOUR_TOKEN" | jq '.[0].code'
# Shopify: Verify inventory levels for a SKU
curl -s "https://YOUR-STORE.myshopify.com/admin/api/2025-01/inventory_levels.json?inventory_item_ids=ID" \
-H "X-Shopify-Access-Token: TOKEN" | jq '.inventory_levels[] | {location_id, available}'
| Component | Current Version | Release Date | Status | Breaking Changes | Notes |
|---|---|---|---|---|---|
| Shopify Admin API | 2025-01 | 2025-01 | Current | REST deprecation push toward GraphQL | Versioned endpoints; 12mo min support |
| Shopify Admin API | 2024-10 | 2024-10 | Supported | N/A | Previous stable version |
| Adobe Commerce | 2.4.7 | 2024-04 | Current | Async bulk API improvements | PHP 8.2+ required |
| BigCommerce REST API | V3 | 2020 (ongoing) | Current | V2 being deprecated | Always use V3 where available |
| NetSuite REST API | 2024.2 | 2024-08 | Current | N/A | SOAP still supported, REST preferred |
| SAP S/4HANA OData | 2408 | 2024-08 | Current | Async OData processing | x-csrf-token required for writes |
| D365 Business Central | API v2.0 | 2023 (ongoing) | Current | N/A | OData v4 with Azure AD auth |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Automating order flow from Shopify/Magento/BigCommerce into any ERP | You only sell on one marketplace (Amazon/eBay) | Marketplace-specific integration patterns |
| Need real-time inventory visibility across online + warehouse | ERP doesn't have an API (legacy on-premise) | File-based integration (CSV/SFTP drop) |
| Processing 100-10,000+ orders/day | < 10 orders/day and manual entry is acceptable | Manual order entry in ERP |
| Running multi-channel ecommerce (multiple storefronts) | Single Shopify store with native inventory | Shopify's built-in inventory management |
| Need automated fulfillment + tracking number pushback | ERP is only for financials, not fulfillment | Simple accounting sync (journal entries only) |
| Capability | Shopify | Adobe Commerce (Magento 2) | BigCommerce | Notes |
|---|---|---|---|---|
| API Style | REST + GraphQL | REST + GraphQL + SOAP | REST (V2/V3) + GraphQL (Storefront) | Shopify pushing GraphQL as primary |
| Rate Limits | 100-1,000 pts/s (GraphQL) | Disabled by default (configurable) | 150-450 req/30s by plan | Magento most permissive; Shopify most structured |
| Webhook Support | Excellent (HMAC signed) | Via observers + message queue | Good (no signature) | Shopify webhooks most reliable |
| Bulk Operations | GraphQL Bulk Operations | Async bulk REST endpoints | No native bulk API | Shopify best for large catalog updates |
| Auth Model | OAuth 2.0 / API key | OAuth 1.0a / bearer token | OAuth + API token | Magento's OAuth 1.0a is oldest pattern |
| Inventory API | inventorySetQuantities (GraphQL) | POST /V1/inventory/source-items | PUT /v3/inventory/items | Shopify: multi-location native |
| Order API | Orders + Fulfillment Orders | Orders + Shipments | Orders V2/V3 + Shipments | Shopify fulfillment_orders most flexible |
| Multi-store | Shopify Markets (single admin) | Multi-website architecture | Multi-storefront | Magento most flexible for B2B |
| Native ERP Connectors | Global ERP Program | Adobe Commerce connectors | App marketplace | Shopify most mature partner program |
| Pagination | Cursor-based (page_info) | Page-based (searchCriteria) | Page-based (page + limit) | Cursor-based most scalable |