API · Acumatica

Complete Guide to Acumatica REST API Integration

A practical, end-to-end guide to integrating with the Acumatica REST API — authentication, endpoint discovery, real-world request patterns, and production-grade error handling.

John Kihiu12 min read

Building an integration against Acumatica's REST API is mostly about a handful of decisions made once, early, that either save you months of maintenance or cost you months of it: how you authenticate, how you version your endpoint, how you handle failure, and how you keep the integration idempotent enough to survive being re-run.

Start with a custom endpoint, not Default

Before writing a line of integration code, clone the built-in Default endpoint into a named one from Web Service Endpoints — MyIntegration, version 1.0. Every field you'll ever need, including custom ones, gets added there. This single decision is what insulates the integration from Acumatica platform upgrades silently changing the shape of the contract you built against.

Authenticate with OAuth 2.0

Register a connected application on the instance and use the OAuth 2.0 token flow rather than the older cookie-session login. It's stateless per request, doesn't tie you to session affinity behind a load balancer, and the token's expiry is explicit rather than an opaque session timeout you discover by having a call suddenly fail.

C# · AUTHENTICATED CLIENT SETUP
var tokenResponse = await httpClient.PostAsync(
    $"{baseUrl}/identity/connect/token",
    new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "password",
        ["client_id"] = clientId,
        ["client_secret"] = clientSecret,
        ["username"] = apiUser,
        ["password"] = apiUserPassword,
        ["scope"] = "api"
    }));

var token = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>();
httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", token.AccessToken);

Design writes to be idempotent

Because every write is a PUT that upserts on key fields, always include the natural key in write payloads — even for records you're certain are new. That one habit is what lets a retried request after a timeout resolve to "update, no-op" instead of "duplicate record," which matters the first time a batch job gets interrupted midway and needs to be safely re-run.

Page deliberately — don't assume consistency

Use $top/$skip for paging, but filter on a stable, monotonic field like a last-modified timestamp for anything that has to be exhaustive — raw offset paging against a table that's being written to concurrently can skip or duplicate rows across the page boundary.

Separate transient failures from real ones

A 400 with a PXException validation message will fail identically on every retry — log it and route it to a dead-letter path, don't retry it. A timeout or 5xx is a reasonable retry candidate with exponential backoff. Conflating the two is the most common reliability bug in integrations built against this API.

There's no documented rate limit — there's a concurrency ceiling

Acumatica's API throttling comes from a license-defined limit on concurrent API "cores," not a requests-per-minute quota. Bound your own client concurrency with a semaphore rather than firing unlimited parallel requests and hoping the API rejects cleanly — it queues instead, which looks like a hang, not a rate-limit error.

Watch the contract version across upgrades

Because your endpoint is versioned independently of the platform, an Acumatica upgrade won't silently break you — but a deliberate version bump of your own endpoint should be tested against a copy of the instance first, comparing the generated OpenAPI spec of old and new versions to catch field or structure changes before they hit production traffic.

Wrapping up

A durable Acumatica REST integration is built on a handful of unglamorous decisions made up front: a dedicated endpoint instead of Default, OAuth 2.0 over cookie sessions, idempotent PUT payloads, timestamp-based paging, and retry logic that actually distinguishes a validation error from a transient one. None of it is exotic — it's just easy to skip until the integration has been in production long enough for the shortcuts to matter.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.