Production engineering guide for erp api integration covering patterns, implementation strategies, and operational best practices.
Enterprise Resource Planning (ERP) systems are the backbone of financial, supply chain, and operational data. When integrating ERP data with external systems—such as CRM, e-commerce, or custom analytics engines—API integration is the primary mechanism. This page details the design, deployment, and operational practices for building and maintaining robust ERP API integrations, with focus on real-world challenges and concrete implementation patterns.
An ERP API integration begins with a shared contract: the specification of endpoints, request and response shapes, error semantics, and versioning. The contract is not a document; it is a contract enforced by HTTP.
All ERP APIs expose resources via standard REST conventions: GET /api/v1/sales-orders, POST /api/v1/invoices, PATCH /api/v1/inventory/{id}. Requests use application/json with a Content-Type header and Accept header to negotiate response format.
Requests and responses follow a consistent shape, typically:
{
"id": "SO-2024-00123",
"created": "2024-04-15T08:30:00Z",
"status": "Posted",
"customerId": "CUST-0045",
"lines": [
{
"itemId": "ITEM-102",
"quantity": 12.5,
"unitPrice": 24.95,
"description": "Wireless keyboard, 80-key"
}
],
"metadata": {
"sourceSystem": "OrderFlow",
"externalId": "OF-847293"
}
}
The metadata field is used for audit and orchestration. It is not optional. It is required in every integration that involves synchronization, and missing metadata leads to undetected drift between systems.
ERP APIs are versioned using URL path (/api/v1/...) or Accept header (application/vnd.company.erp+json; version=1.1). The client must specify the version in the Accept header:
Accept: application/vnd.company.erp+json; version=1.2
The server responds with a Vary: Accept header and includes Link headers for version discovery:
Link: <https://erp.company.com/api/v1>; rel="self"
Link: <https://erp.company.com/api/v1/docs>; rel="documentation"
Link: <https://erp.company.com/api/v1/changelog>; rel="changelog"
When a client sends an unknown version (e.g., version=1.5), the server returns 406 Not Acceptable with a Vary: Accept header and a body describing available versions:
{
"availableVersions": [
{ "version": "1.0", "status": "stable" },
{ "version": "1.1", "status": "stable" },
{ "version": "1.2", "status": "beta" },
{ "version": "1.3", "status": "experimental" }
],
"recommendedVersion": "1.2"
}
Clients must handle version negotiation and degrade gracefully when the ERP API introduces breaking changes.
API integrations rely on HTTP-based authentication. The most common patterns are:
Authorization: Bearer <token>X-API-Key headerclient_credentials grant type for server-to-server useClients obtain a token via a /oauth/token endpoint:
POST /oauth/token HTTP/1.1
Host: erp.company.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=app-orderflow&client_secret=abc123xyz
On success:
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxxx.yyyyyy",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "sales.read sales.write inventory.read"
}
The token is then used in subsequent API calls:
GET /api/v1/sales-orders HTTP/1.1
Host: erp.company.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxxx.yyyyyy
X-API-Key: app-orderflow-123
Accept: application/vnd.company.erp+json; version=1.2
Tokens expire after expires_in seconds. Clients must refresh them before they expire. A common failure is sending an expired token without refreshing:
{
"error": "invalid_token",
"error_description": "The access token expired at 2024-04-15T10:00:00Z",
"expires_at": "2024-04-15T10:00:00Z"
}
Clients must detect 401 Unauthorized with WWW-Authenticate: Bearer and retry with a fresh token. The WWW-Authenticate header includes the realm and error information:
WWW-Authenticate: Bearer realm="ERP API", error="invalid_token", error_description="Token expired"
A silent failure occurs when the client uses Authorization: Bearer but the server expects X-API-Key. The response is 401 Unauthorized with no body, and the client logs Auth method mismatch: expected X-API-Key, got Bearer.
ERP integrations must handle both real-time and batch operations. The distinction is not in the API shape but in the execution model.
For real-time integrations (e.g., order creation in an e-commerce platform), a client sends a single POST /api/v1/sales-orders request:
{
"customerId": "CUST-0045",
"lines": [
{
"itemId": "ITEM-102",
"quantity": 1,
"unitPrice": 24.95
}
],
"metadata": {
"sourceSystem": "WebStore",
"externalId": "WS-789123"
}
}
The ERP responds with 201 Created and the full resource, including an id assigned by the ERP:
HTTP/1.1 201 Created
Location: https://erp.company.com/api/v1/sales-orders/SO-2024-00123
Content-Type: application/json
{
"id": "SO-2024-00123",
"status": "Draft",
"created": "2024-04-15T08:30:00Z",
"customerId": "CUST-0045",
"lines": [
{
"itemId": "ITEM-102",
"quantity": 1,
"unitPrice": 24.95,
"description": "Wireless keyboard, 80-key"
}
],
"metadata": {
"sourceSystem": "WebStore",
"externalId": "WS-789123"
}
}
The client must handle 400 Bad Request for malformed input:
{
"error": "validation_failed",
"details": [
{
"field": "customerId",
"message": "Customer ID must be at least 6 characters"
},
{
"field": "lines[0].quantity",
"message": "Quantity must be positive"
}
]
}
For nightly batch syncs (e.g., inventory updates from warehouse devices), integrations use a two-phase model:
POST /api/v1/changesPOST /api/v1/changes HTTP/1.1
Host: erp.company.com
Authorization: Bearer <token>
Content-Type: application/json
{
"jobId": "BATCH-2024-04-15-01",
"timestamp": "2024-04-15T04:00:00Z",
"changes": [
{
"type": "inventory.update",
"itemId": "ITEM-102",
"quantity": 25,
"location": "WH-01",
"reason": "Receiving"
},
{
"type": "sales.order.create",
"customerId": "CUST-0045",
"lines": [
{
"itemId": "ITEM-103",
"quantity": 3,
"unitPrice": 19.99
}
],
"metadata": {
"sourceSystem": "WarehouseMobile",
"externalId": "WM-00123"
}
}
]
}
ERP responds with 202 Accepted and includes a Location header:
HTTP/1.1 202 Accepted
Location: https://erp.company.com/api/v1/jobs/BATCH-2024-04-15-01
Content-Type: application/json
{
"jobId": "BATCH-2024-04-15-01",
"status": "Processing",
"queuedAt": "2024-04-15T04:00:00Z",
"estimatedCompletion": "2024-04-15T04:15:00Z"
}
The client polls the job endpoint:
GET /api/v1/jobs/BATCH-2024-04-15-01 HTTP/1.1
Host: erp.company.com
Authorization: Bearer <token>
Response:
{
"jobId": "BATCH-2024-04-15-01",
"status": "Completed",
"startedAt": "2024-04-15T04:00:00Z",
"completedAt": "2024-04-15T04:14:32Z",
"summary": {
"totalChanges": 142,
"successful": 138,
"failed": 4,
"errors": [
{
"changeId": 56,
"type": "sales.order.create",
"error": "customer_not_found",
"message": "Customer CUST-1000 does not exist",
"details": {
"customerId": "CUST-1000"
}
}
]
}
}
A common failure: the ERP processes the batch but fails to update the job status. The client polls and receives a 404 Not Found for the job, even though the job was created. This happens when the ERP uses a two-phase commit: first, it writes the job metadata to a database, then it applies changes and updates the job. If the process crashes between these phases, the job appears in the list but has no final status.
To detect this, clients should use ETag and If-None-Match headers:
GET /api/v1/jobs/BATCH-2024-04-15-01 HTTP/1.1
Host: erp.company.com
Authorization: Bearer <token>
If-None-Match: "e3b0c44298fc1c11"
On first call, the server sets ETag: "e3b0c44298fc1c11" and returns 200 OK. On subsequent calls, if the job has changed, the server returns 304 Not Modified and updates the ETag.
ERP API integrations must be resilient to transient failures. Idempotency is the cornerstone of reliable integration.
Clients use Idempotency-Key headers to ensure that repeated requests produce the same result:
POST /api/v1/sales-orders HTTP/1.1
Host: erp.company.com
Authorization: Bearer <token>
Idempotency-Key: ws-789123-123456789
Content-Type: application/json
{
"customerId": "CUST-0045",
"lines": [...]
}
The ERP stores the Idempotency-Key and returns 200 OK if the request was processed before, or 201 Created if it is new.
{
"id": "SO-2024-00123",
"status": "Draft",
"created": "2024-04-15T08:30:00Z",
"metadata": {
"sourceSystem": "WebStore",
"externalId": "WS-789123"
},
"isFirstTime": false
}
The isFirstTime field indicates whether the ERP created the resource or used an existing one.
All ERP APIs return standardized error responses:
{
"error": "invalid_request",
"message": "One or more fields failed validation",
"details": [
{
"field": "customerId",
"value": "CUST-100",
"code": "invalid_format",
"message": "Customer ID must start with 'CUST-'"
}
],
"correlationId": "c7a9b1e5-4f6d-4a3b-9c2a-1d2e3f4a5b6c"
}
Clients must log correlationId and use it to trace the request through the ERP’s internal logs.
Clients use exponential backoff with jitter:
On failure, the client logs:
[ERP API] Error: 503 Service Unavailable, retrying in 7s
[ERP API] Retrying POST /api/v1/sales-orders (attempt 3), correlationId=c7a9b1e5-4f6d-4a3b-9c2a-1d2e3f4a5b6c
When a request times out (e.g., 30s), the client should re-send the same request with Idempotency-Key and X-Request-ID headers, and the ERP should respond with 202 Accepted even if the request is still being processed.
A well-integrated ERP API must be monitored. The integration is healthy when:
200 OK in under 500msA health check endpoint is exposed at /health:
GET /health HTTP/1.1
Host: erp.company.com
Authorization: Bearer <token>
Response:
{
"status": "healthy",
"checks": [
{
"name": "api_ping",
"status": "pass",
"latencyMs": 120,
"timestamp": "2024-04-15T08:30:00Z"
},
{
"name": "token_validity",
"status": "pass",
"expiresAt": "2024-04-15T10:00:00Z",
"timestamp": "2024-04-15T08:30:00Z"
},
{
"name": "batch_job_lag",
"status": "warning",
"message": "Last batch job took 14 minutes, expected 10",
"timestamp": "2024-04-15T04:14:32Z"
}
],
"lastSync": "2024-04-15T04:14:32Z"
}
Clients use this to trigger alerts and dashboards. A 503 Service Unavailable from /health indicates a deeper issue: the ERP database is down, or the API server is overloaded.
metadata field: leads to undetected drift between systems; the ERP creates a sales order, but the client cannot correlate it to the original source.Content-Type: client sends application/json but server expects text/json, leading to parsing errors and 400 Bad Request.ETag in polling: clients miss updates, leading to stale data.These patterns, when followed, ensure a reliable, observable, and maintainable ERP API integration.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy