The Acumatica contract-based REST API is the cleanest way to integrate external systems with Acumatica ERP. Whether you're pushing invoices from an e-commerce store, pulling customer data into a CRM, or syncing inventory with a warehouse, the contract-based API is the right tool.

This guide walks through what I've learned shipping integrations against this API for production tenants — from the authentication choices that actually matter, to the patterns that prevent you waking up at 3 AM to retry batches.

Who this is for

Developers who already know REST and JSON, but are new to Acumatica's specific dialect. Examples are language-agnostic but I'll use C# and PHP where it helps.

Authentication

Acumatica supports two main authentication flows for the contract-based API. Pick based on whether your integration is server-to-server (cookie or OAuth client credentials) or user-facing (OAuth authorization code).

The classic approach. You POST credentials to /entity/auth/login, receive a session cookie, and include it in subsequent requests. Always remember to call /logout when you're done — Acumatica counts active sessions against your licence.

HTTP · LOGIN
POST /entity/auth/login // returns Set-Cookie: ASP.NET_SessionId=...
Content-Type: application/json

{
  "name": "admin",
  "password": "********",
  "company": "MyTenant",
  "branch": "MAIN"
}

OAuth 2.0

For anything customer-facing or multi-tenant, OAuth is the better answer. Acumatica supports the standard authorization code grant. You register a client in the Connected Applications screen, then redirect users through the standard consent flow.

C# · TOKEN REQUEST
var client = new HttpClient();
var body = new FormUrlEncodedContent(new[] {
    new KeyValuePair<string,string>("grant_type", "authorization_code"),
    new KeyValuePair<string,string>("code", code),
    new KeyValuePair<string,string>("client_id", clientId),
    new KeyValuePair<string,string>("client_secret", secret),
    new KeyValuePair<string,string>("redirect_uri", callback)
});

var resp = await client.PostAsync("/identity/connect/token", body);
var token = await resp.Content.ReadFromJsonAsync<TokenResponse>();
// Use token.access_token in Authorization: Bearer header

Endpoint discovery

Every contract-based endpoint lives at /entity/{Endpoint}/{Version}/{Entity}. The default endpoint is Default, but you can — and should — publish your own custom endpoint for production work.

  • Default endpoint: always available, mirrors out-of-box screens.
  • Custom endpoint: shape only the fields and entities you need. Faster, smaller, and protected from base-system schema drift.

Always build a custom endpoint for new integrations. It pays for itself on the first upgrade.

CRUD patterns

The contract-based API uses a JSON shape Acumatica calls nested entities. Every field is wrapped in { "value": ... }. Cumbersome? Yes. But it's how partial updates and conditional patches are expressed cleanly.

PHP · CREATE CUSTOMER
$payload = [
    'CustomerID'   => ['value' => 'JOHNK001'],
    'CustomerName' => ['value' => 'John Kihiu Ltd.'],
    'CustomerClass'=> ['value' => 'DEFAULT'],
    'MainContact'  => [
        'Email' => ['value' => 'kihiujohn12@gmail.com'],
        'Phone1'=> ['value' => '+254115169705'],
    ],
];

$response = $client->put('/entity/Default/24.200.001/Customer', [
    'json' => $payload,
    'cookies' => $jar,
]);

Notice we use PUT, not POST. In Acumatica, PUT is upsert — create if missing, update if present. POST is reserved for invoking actions.

Invoking actions

Actions like Release, Approve, Cancel, or Email Invoice are exposed as POST endpoints under /{Entity}/{Action}. The body identifies the record by key.

HTTP · RELEASE INVOICE
POST /entity/Default/24.200.001/SalesInvoice/ReleaseSalesInvoice

{
  "entity": {
    "ReferenceNbr": { "value": "INV001234" },
    "Type":         { "value": "Invoice" }
  }
}

Actions are asynchronous. The response is 202 Accepted with a Location header. Poll that location until you get 204 No Content.

Error handling

Acumatica returns three classes of errors you must handle distinctly:

  1. 4xx with a body — validation errors. Parse exceptionMessage and surface it.
  2. 500 Internal Server Error — almost always a stale session or licence issue. Re-login and retry once.
  3. Concurrency exceptions — happen on heavy writes. Always re-fetch and retry with the new ETag.
Watch your session count

Cookie sessions are billed per licence concurrent-user. Always log out, even on errors. A naive retry loop can starve your tenant of sessions in minutes.

Production tips

  • Always use a custom endpoint. The Default endpoint changes between Acumatica versions; your custom one is yours forever.
  • Throttle outgoing calls. 10–20 concurrent requests max. Acumatica is more sensitive to write throughput than read.
  • Log the request and response in full for the first month after going live. The bugs you find are never the bugs you expected.
  • Idempotency keys. Use the customer's order ID as your ExternalRefNbr and dedupe on it.

That's the rough shape of every production-grade Acumatica integration I've shipped. Adapt it, and your client will sleep better than mine did the first time around.