Erp Api Integration

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.

Designing the Integration Contract

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.

Request and Response Structure

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.

Versioning and Backward Compatibility

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.

Authentication and Authorization

API integrations rely on HTTP-based authentication. The most common patterns are:

Bearer Token Flow

Clients 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

Token Refresh and Failure Modes

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.

Handling Batch and Real-Time Operations

ERP integrations must handle both real-time and batch operations. The distinction is not in the API shape but in the execution model.

Real-Time: Single-Record Sync

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"
    }
  ]
}

Batch Processing: Large-Scale Data Sync

For nightly batch syncs (e.g., inventory updates from warehouse devices), integrations use a two-phase model:

  1. Upload phase: client sends a list of changes via POST /api/v1/changes
  2. Apply phase: ERP processes the batch and returns a job ID

Upload Phase

POST /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"
}

Apply Phase

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"
        }
      }
    ]
  }
}

Failure Handling in Batch Jobs

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.

Error Handling and Idempotency

ERP API integrations must be resilient to transient failures. Idempotency is the cornerstone of reliable integration.

Idempotency Keys

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.

Error Responses and Retry Logic

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.

Retry Strategy

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.

Monitoring and Health Checks

A well-integrated ERP API must be monitored. The integration is healthy when:

A 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.

Common Pitfalls and Silent Failures

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.