Acumatica REST API: Contract-Based Versioning, Capabilities, and Integration Guide

Type: ERP Integration System: Acumatica Cloud ERP (2024 R2 / 24.200.001) Confidence: 0.88 Sources: 8 Verified: 2026-03-02 Freshness: 2026-03-02

TL;DR

System Profile

Acumatica Cloud ERP is a mid-market, cloud-native ERP platform built on a .NET/SQL Server stack. Its contract-based REST API was introduced to replace the legacy screen-based SOAP API, providing stability against UI customizations and platform upgrades. The contract-based approach means API endpoints expose business logic objects (not screen fields), so changes to forms, localizations, or customization projects do not break existing API integrations. This card covers the REST API surface across all Acumatica editions. The OData interface (GI-based and DAC-based) is a separate read-oriented surface not covered in depth here. [src1, src4]

PropertyValue
VendorAcumatica
SystemAcumatica Cloud ERP 2024 R2
API SurfaceREST (Contract-Based)
Current API VersionDefault endpoint 24.200.001
Editions CoveredGeneral Business, Distribution, Manufacturing, Construction, Retail Commerce
DeploymentCloud (SaaS) / Private Cloud / On-Premise
API DocsAcumatica Help — Contract-Based REST API
StatusGA

API Surfaces & Capabilities

Acumatica provides multiple integration surfaces. The contract-based REST API is the primary and recommended surface for new integrations. [src1, src3, src4]

API SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
Contract-Based REST APIHTTPS/JSONFull CRUD, actions, file attachmentsConfigurable via $topLicense-tier (100-150/min)YesPartial (paged)
OData (GI-Based)HTTPS/JSON+ODataReporting, BI tools (Power BI, Excel)Configurable via $topShared with RESTYesRead-only
OData (DAC-Based)HTTPS/JSON+ODataDirect table access, deleted record trackingConfigurable via $topShared with RESTYesRead-only
Screen-Based SOAP APISOAP/XMLLegacy integrations (deprecated for new dev)N/ASharedYesNo
Push NotificationsHTTP callbacksReal-time change detectionN/AN/AYesN/A
WebhooksHTTP POST (inbound)External system pushes data into AcumaticaN/AN/AYesN/A

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Max records per query pageConfigurable via $topREST APIDefault page size varies by entity; use $top and $skip for pagination [src1]
Max $expand depth2 levelsREST APINested expansions beyond 2 levels return errors [src2]
Native batch operationsNot supportedREST APINo OData $batch equivalent; use sequential calls [src1]
Request body sizeNot officially publishedREST APILarge file attachments may hit IIS/web server limits

Rolling / Daily Limits

Limit TypeValueWindowEdition Differences
API requests per minute~100 (standard), ~150 (L-series)Per minuteLicense-tier-dependent; configurable only by Acumatica support [src6, src7]
Concurrent API sessions3 (standard), 6 (L-series)Per instanceEach unauthenticated request counts as a new session if cookies are not reused [src6]
Maximum web service API usersLicense-dependentPer instanceChecked on License Monitoring Console (SM604000) [src1]
Session timeout~20 minutes idlePer sessionCookie-based sessions expire; OAuth tokens depend on configuration [src2]

Authentication

Acumatica supports OAuth 2.0 and cookie-based authentication. OAuth is recommended for production integrations. Client applications are registered on the Connected Applications screen (SM303010). [src1, src3]

FlowUse WhenToken LifetimeRefresh?Notes
OAuth 2.0 Authorization CodeUser-context web applications, interactive integrationsConfigurable (~20 min session)Yes (with offline_access scope)Requires redirect URI; user authenticates via Acumatica login page [src1]
OAuth 2.0 Resource Owner PasswordServer-to-server where auth code is not feasibleConfigurableYes (with offline_access scope)Sends username/password directly; less secure [src1]
OAuth 2.0 ImplicitSingle-page apps (SPAs)Short-livedNoTokens exposed in URL fragment; not recommended for production [src1]
Cookie-Based LoginSimple integrations, quick prototyping~20 minutes idle timeoutNo (re-login required)POST to /entity/auth/login; must explicitly logout to free session [src2]

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START — User needs to integrate with Acumatica ERP
|-- What's the integration pattern?
|   |-- Real-time (<1s) --> Contract-Based REST API CRUD [src1]
|   |   |-- Need business actions (release, approve)? --> REST API actions
|   |   |-- Need change notifications? --> Push Notifications (SM302000)
|   |-- Batch/Bulk (scheduled, high volume)
|   |   |-- > 100K records/day? --> Import Scenarios or file-based
|   |   |-- < 100K records/day --> REST API with client-side batching
|   |-- Event-driven --> Push Notifications (outbound) or Webhooks (inbound)
|   |-- File-based --> Acumatica Import Scenarios (CSV/Excel)
|-- Which direction?
|   |-- Inbound (writing) --> PUT with rate limiting
|   |-- Outbound (reading) --> GET with $filter + pagination
|   |-- Bidirectional --> LastModifiedDateTime + conflict resolution
|-- Error tolerance?
    |-- Zero-loss --> Idempotency checks + logging
    |-- Best-effort --> Retry on 4xx/5xx with backoff

Quick Reference

OperationMethodEndpointPayloadNotes
Get all recordsGET/entity/Default/24.200.001/{Entity}N/AUse $top and $skip for pagination
Get single recordGET/entity/Default/24.200.001/{Entity}/{ID}N/AID is the internal GUID
Get by key fieldsGET/entity/Default/24.200.001/{Entity}/{KeyValue}N/Ae.g., /SalesOrder/SO/000042
Create recordPUT/entity/Default/24.200.001/{Entity}JSONFields wrapped in {"value": "..."}
Update recordPUT/entity/Default/24.200.001/{Entity}JSONInclude key fields + changed fields
Delete recordDELETE/entity/Default/24.200.001/{Entity}/{ID}N/ANot all entities support delete
Execute actionPOST/entity/Default/24.200.001/{Entity}/{ID}/action/{ActionName}JSON (params)e.g., ReleaseDocument, ProcessPayment
Filter recordsGET/entity/Default/24.200.001/{Entity}?$filter=...N/AOData-style: eq, gt, lt, contains
Expand relatedGET/entity/Default/24.200.001/{Entity}?$expand=DetailsN/AMax 2 levels deep
Attach filePUT/entity/Default/24.200.001/{Entity}/{ID}/files/{filename}BinaryContent-Type matches file type
Login (cookie)POST/entity/auth/loginJSON credentialsReturns session cookie
Logout (cookie)POST/entity/auth/logoutN/AAlways call to free session

Step-by-Step Integration Guide

1. Register OAuth application in Acumatica

Navigate to System > Integration > Configure > Connected Applications (SM303010). Create a new application, select the OAuth 2.0 flow type, and configure the redirect URI. Copy the generated Client ID and create a shared secret. [src1]

Steps in Acumatica:
1. Navigate to System > Integration > Configure > Connected Applications
2. Click "+" to add new application
3. Enter Client Name (e.g., "MyIntegration")
4. Select OAuth 2.0 Flow: "Authorization Code" or "Resource Owner Password Credentials"
5. Add redirect URI (for auth code flow): https://myapp.com/callback
6. Click "Add Shared Secret" on the Secrets tab
7. Copy the Client ID and Secret Value immediately
8. Save the form

Verify: The application appears in the Connected Applications list with status Active.

2. Authenticate and obtain access token

Use the OAuth 2.0 resource owner password flow for server-to-server integrations. The token endpoint is at {instance}/identity/connect/token. [src1]

curl -X POST "https://yourinstance.acumatica.com/identity/connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "username=admin" \
  -d "password=YourPassword" \
  -d "scope=api"

Verify: Response contains an access_token field.

3. Retrieve records with filtering and pagination

Use GET with OData-style query parameters. Implement pagination using $top and $skip. [src1, src2]

curl -X GET "https://yourinstance.acumatica.com/entity/Default/24.200.001/SalesOrder?\$filter=Status%20eq%20'Open'&\$top=50&\$skip=0" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"

Verify: Response is a JSON array. Fewer than $top records indicates the last page.

4. Create and update records

Use PUT for both create and update operations. All field values must be wrapped in {"value": "..."} objects. [src2]

curl -X PUT "https://yourinstance.acumatica.com/entity/Default/24.200.001/Customer" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "CustomerID": {"value": "NEWCUST01"},
    "CustomerName": {"value": "New Customer LLC"},
    "CustomerClass": {"value": "DEFAULT"}
  }'

Verify: Response returns the full record with all fields populated.

5. Execute business actions

Use POST to invoke business actions on records, such as releasing invoices. [src1]

curl -X POST "https://yourinstance.acumatica.com/entity/Default/24.200.001/Invoice/INV000042/action/ReleaseInvoice" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

Verify: GET the record again and confirm the status has changed.

6. Implement error handling and rate limit retries

Parse nested error details from Acumatica responses. Implement exponential backoff for 429 responses. [src5, src6]

import requests, time

def acumatica_request(method, url, token, data=None, max_retries=5):
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    for attempt in range(max_retries):
        response = requests.request(method, url, headers=headers, json=data)
        if response.status_code in (200, 201, 204):
            return response.json() if response.content else None
        if response.status_code == 429:
            wait = min(2 ** attempt * 2, 60)
            time.sleep(wait)
            continue
        if response.status_code == 400:
            error_body = response.json()
            raise Exception(f"Validation: {error_body.get('message', 'Unknown')}")
        raise Exception(f"API error {response.status_code}: {response.text}")
    raise Exception("Max retries exceeded")

Verify: Test with an invalid field to confirm error parsing.

Code Examples

Python: Retrieve and paginate all sales orders

# Input:  Acumatica instance URL, access token
# Output: List of all open sales orders across all pages

import requests

def get_all_sales_orders(instance_url, token, status="Open", page_size=100):
    headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
    all_orders, skip = [], 0
    while True:
        url = (f"{instance_url}/entity/Default/24.200.001/SalesOrder"
               f"?$filter=Status eq '{status}'&$top={page_size}&$skip={skip}")
        page = requests.get(url, headers=headers).json()
        if not page: break
        all_orders.extend(page)
        if len(page) < page_size: break
        skip += page_size
    return all_orders

JavaScript/Node.js: Create a customer with error handling

// Input:  Acumatica instance URL, access token, customer data
// Output: Created customer record or error details

// npm install [email protected]
const axios = require("axios");

async function createCustomer(instanceUrl, token, customerData) {
  const payload = {};
  for (const [key, val] of Object.entries(customerData)) {
    payload[key] = { value: val };
  }
  try {
    const resp = await axios.put(
      `${instanceUrl}/entity/Default/24.200.001/Customer`,
      payload,
      { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } }
    );
    return resp.data;
  } catch (err) {
    if (err.response?.status === 429) console.error("Rate limited");
    throw err;
  }
}

cURL: Quick API connectivity test

# Cookie-based login
curl -c cookies.txt -X POST \
  "https://yourinstance.acumatica.com/entity/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"name":"admin","password":"YourPassword","company":"MyCompany"}'

# Retrieve stock items
curl -b cookies.txt "https://yourinstance.acumatica.com/entity/Default/24.200.001/StockItem?\$top=5"

# Always logout to free session
curl -b cookies.txt -X POST "https://yourinstance.acumatica.com/entity/auth/logout"

Data Mapping

Field Mapping Reference

Acumatica Field PatternAPI RepresentationTypeTransformGotcha
CustomerID{"CustomerID": {"value": "CUST01"}}String (key)Wrapped in value objectMust match exact case and format
OrderTotal{"OrderTotal": {"value": 1500.00}}DecimalWrapped in value objectCurrency decimals depend on config
OrderDate{"OrderDate": {"value": "2026-03-02T00:00:00"}}DateTimeISO 8601Time zone depends on branch config
Status{"Status": {"value": "Open"}}String (enum)Use display valueAvailable values vary by entity
Detail lines{"Details": [{"InventoryID": {"value": "ITEM01"}}]}ArrayNested value objectsChild records follow same wrapping
Custom fields{"custom": {"FieldName": {"value": "..."}}}VariesWrapped under custom keySeparate path from standard fields
NoteID (GUID){"id": "a1b2c3d4-..."}GUIDNot wrappedSystem-generated; used for GET by ID

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
400Validation errorMissing required field, wrong value formatParse nested error details for field-level messages [src5]
401Authentication failureExpired token, invalid credentialsRe-authenticate; request a new token [src2]
403Insufficient permissionsAPI user lacks entity/field accessCheck user role permissions in security config
404Not foundWrong endpoint version, misspelled entityVerify endpoint version matches instance [src2]
429Rate limit exceededMore requests/min than license allowsExponential backoff; upgrade license tier [src6, src7]
500Internal server errorBusiness logic exception, DB constraintCheck Acumatica system trace; may require support ticket

Failure Points in Production

Anti-Patterns

Wrong: Sending flat field values without wrapper

// BAD — Acumatica requires {"value": "..."} wrapping
{
  "CustomerID": "CUST01",
  "CustomerName": "My Customer"
}

Correct: Wrap every field value in a value object

// GOOD — Proper Acumatica REST API field format
{
  "CustomerID": {"value": "CUST01"},
  "CustomerName": {"value": "My Customer"}
}

Wrong: Not logging out of cookie-based sessions

# BAD — session leak: if any call fails, logout never runs
session.post(f"{url}/entity/auth/login", json=credentials)
data = session.get(f"{url}/entity/Default/24.200.001/Customer").json()
process(data)
session.post(f"{url}/entity/auth/logout")

Correct: Use try/finally to guarantee logout

# GOOD — logout runs even if processing fails
session.post(f"{url}/entity/auth/login", json=credentials)
try:
    data = session.get(f"{url}/entity/Default/24.200.001/Customer").json()
    process(data)
finally:
    session.post(f"{url}/entity/auth/logout")

Wrong: Hardcoding endpoint version

# BAD — breaks when Acumatica is upgraded
endpoint = f"{instance}/entity/Default/24.200.001/SalesOrder"

Correct: Make endpoint version configurable

# GOOD — version stored in configuration
import os
API_VERSION = os.environ.get("ACUMATICA_API_VERSION", "24.200.001")
endpoint = f"{instance}/entity/Default/{API_VERSION}/SalesOrder"

Common Pitfalls

Diagnostic Commands

# Test OAuth authentication
curl -s -X POST "https://yourinstance.acumatica.com/identity/connect/token" \
  -d "grant_type=password&client_id=CLIENT_ID&client_secret=SECRET&username=admin&password=Pass&scope=api"

# List available entities on endpoint
curl -s "https://yourinstance.acumatica.com/entity/Default/24.200.001" \
  -H "Authorization: Bearer $TOKEN" | python3 -m json.tool

# Verify entity field schema
curl -s "https://yourinstance.acumatica.com/entity/Default/24.200.001/Customer/\$adHocSchema" \
  -H "Authorization: Bearer $TOKEN" | python3 -m json.tool

# Test cookie-based login
curl -c cookies.txt -s -X POST \
  "https://yourinstance.acumatica.com/entity/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"name":"admin","password":"Pass","company":"MyCompany"}'

# Check license limits: System > Licensing > License Monitoring Console (SM604000)

Version History & Compatibility

Endpoint VersionAcumatica ReleaseStatusKey ChangesMigration Notes
24.200.0012024 R2CurrentNew OData URLs; OpenAPI 3.0 support; deleted record trackingLegacy OData URLs deprecated, sunset 2025 R2
23.200.0012023 R2SupportedAdditional manufacturing and distribution entitiesSame as 2024 R1 endpoint version
22.200.0012022 R2SupportedDAC Schema Browser; expanded default entitiesMinimum version for many ISV integrations
20.200.0012020 R2LegacyPush notifications and webhooks maturedConsider upgrading
18.200.0012018 R2EOLEarly contract-based REST APIUpgrade required

Deprecation Policy

Acumatica supports previous endpoint versions for backward compatibility — calling an older endpoint version on a newer instance continues to work. However, new entities and fields are only available on the latest endpoint version. Acumatica typically provides at least one major release cycle (6-12 months) notice before removing deprecated features. [src1, src8]

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Full CRUD operations on business entitiesRead-only reporting or BI dashboardsOData interface (GI-based or DAC-based) for Power BI/Excel
Executing business actions (release, approve, process)Bulk data migration of >100K recordsAcumatica Import Scenarios (built-in CSV/Excel import)
Building real-time integrations with external systemsSimple field customization without external integrationAcumatica Customization Framework
Server-to-server integration requiring stable versioned contractsDirect database access or SQL queriesN/A — never use direct DB access with Acumatica Cloud
Attaching files and documents programmaticallyLegacy screen-scraping integrationsContract-based REST API replaces screen-based SOAP

Important Caveats

Related Units