---
# === IDENTITY ===
id: business/erp-integration/acumatica-rest-api/2026
canonical_question: "What are the Acumatica REST API capabilities and how does contract-based versioning work?"
aliases:
  - "Acumatica contract-based REST API endpoints and versioning"
  - "How to integrate with Acumatica ERP using the REST API"
  - "Acumatica Web Services API rate limits and authentication"
  - "Acumatica REST API CRUD operations, pagination, and OData"
entity_type: erp_integration
domain: business > erp-integration > acumatica-rest-api
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Acumatica Cloud ERP"
    vendor: "Acumatica"
    version: "2024 R2 (Default endpoint 24.200.001)"
    edition: "All editions (General Business, Distribution, Manufacturing, Construction, Retail Commerce)"
    deployment: "cloud"
    api_surface: "REST (Contract-Based)"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.88
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "2024 R2 — new OData URLs introduced, legacy OData URLs deprecated (sunset 2025 R2)"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "API rate limits are license-tier-dependent: standard tier allows ~100 requests/min and 3 concurrent API sessions; L-series allows ~150 requests/min and 6 concurrent sessions"
  - "Contract-based endpoints must match the exact Acumatica instance version — using 24.200.001 against a 23.200.001 instance returns 404"
  - "Field values in JSON payloads are wrapped in objects with a 'value' property — e.g., {\"CustomerID\": {\"value\": \"CUST01\"}} — omitting the wrapper causes 400 errors"
  - "OAuth api:concurrent_access scope requires passing session cookies back with each request — failure to do so counts each call as a separate user session against license limits"
  - "Screen-based SOAP API is deprecated for new development — use contract-based REST API instead"
  - "Custom endpoint extensions are instance-specific — they do not transfer between tenants automatically"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs Acumatica screen-based SOAP API (legacy)"
    use_instead: "N/A — see Acumatica Integration Development Guide for SOAP"
  - condition: "User needs OData-only access for Power BI or Excel"
    use_instead: "N/A — see Acumatica OData interface documentation"
  - condition: "User needs a different ERP system REST API"
    use_instead: "business/erp-integration/{system}-rest-api/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "real-time sync (individual records, <1s latency)"
      - "batch/bulk (scheduled, high volume)"
      - "event-driven (push notifications, webhooks)"
      - "file-based (CSV/XML import/export)"
  - key: data_volume
    question: "What's your daily data volume?"
    type: choice
    options:
      - "< 1,000 records/day"
      - "1,000-100,000 records/day"
      - "> 100,000 records/day"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "inbound (writing to Acumatica)"
      - "outbound (reading from Acumatica)"
      - "bidirectional sync"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/acumatica-rest-api/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-02)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/acumatica-authentication/2026"
      label: "Acumatica OAuth 2.0 and cookie-based authentication flows"
  solves:
    - id: "business/erp-integration/netsuite-acumatica-migration/2026"
      label: "NetSuite to Acumatica migration playbook"
  alternative_to:
    - id: "business/erp-integration/netsuite-rest-api/2026"
      label: "NetSuite REST API (SuiteTalk) for mid-market ERP integration"
  often_confused_with:
    - id: "business/erp-integration/acumatica-odata/2026"
      label: "Acumatica OData interface (read-only reporting, not full CRUD)"

# === SOURCES ===
sources:
  - id: src1
    title: "Unlocking Acumatica Web Services: REST API, OData, and Integration Best Practices"
    author: Acumatica
    url: https://www.acumatica.com/blog/acumatica-web-services-rest-api-odata-and-integration-best-practices/
    type: official_docs
    published: 2024-11-15
    reliability: authoritative
  - id: src2
    title: "Acumatica REST API Documentation: Examples & Best Practices"
    author: Concentrus
    url: https://concentrus.com/acumatica-rest-api-documentation/
    type: technical_blog
    published: 2024-09-20
    reliability: moderate_high
  - id: src3
    title: "Acumatica API Guide: How to Get Started"
    author: Tipalti
    url: https://tipalti.com/blog/acumatica-api/
    type: technical_blog
    published: 2024-06-10
    reliability: moderate_high
  - id: src4
    title: "Acumatica Cloud ERP APIs Introduction"
    author: Acumatica
    url: https://www.acumatica.com/blog/yes-we-have-an-api-for-that-an-introduction-to-the-acumatica-cloud-erp-apis/
    type: official_docs
    published: 2024-03-15
    reliability: authoritative
  - id: src5
    title: "Unit Testing and Error Handling in REST API Web Services"
    author: Acumatica
    url: https://www.acumatica.com/blog/unit-testing-and-error-handling-in-rest-api-web-services/
    type: official_docs
    published: 2023-08-20
    reliability: authoritative
  - id: src6
    title: "API Login Limit Error for REST API Calls with Access Token"
    author: Acumatica Community
    url: https://community.acumatica.com/develop-integrations-with-web-services-apis-289/api-login-limit-error-for-rest-api-calls-with-access-token-5552
    type: community_resource
    published: 2023-05-15
    reliability: moderate_high
  - id: src7
    title: "eCommerce and API Limits"
    author: Acumatica Community
    url: https://community.acumatica.com/develop-integrations-with-web-services-apis-289/ecommerce-and-api-limits-15799
    type: community_resource
    published: 2024-08-10
    reliability: moderate
  - id: src8
    title: "Acumatica 2024 R2 Release Notes"
    author: Acumatica
    url: https://www.acumatica.com/cloud-erp-software/acumatica-release-notes/acumatica-2024-r2/
    type: vendor_release_notes
    published: 2024-10-01
    reliability: authoritative
---

# Acumatica REST API: Contract-Based Versioning, Capabilities, and Integration Guide

## TL;DR

- **Bottom line**: Acumatica uses a contract-based REST API where endpoints are versioned by release (e.g., `24.200.001` for 2024 R2). Contracts decouple API objects from UI screens, so customizations and upgrades do not break existing integrations. Use the Default endpoint for standard entities; extend it for custom fields.
- **Key limit**: API rate limits are license-tier-dependent — standard tier: ~100 requests/min, 3 concurrent sessions; L-series: ~150 requests/min, 6 concurrent sessions. These cannot be self-configured — contact Acumatica support for increases. [src6, src7]
- **Watch out for**: JSON field values must be wrapped in `{"value": "..."}` objects — sending flat key-value pairs returns 400 errors. Also, endpoint version must exactly match the instance version or you get 404. [src2]
- **Best for**: Mid-market ERP integrations needing full CRUD operations, action execution (e.g., releasing invoices), and file attachments through a modern REST interface with stable versioned contracts. [src1, src4]
- **Authentication**: OAuth 2.0 (authorization code, resource owner password, implicit) or cookie-based login. OAuth with `api` scope recommended for production; cookie-based requires explicit login/logout session management. [src1, src3]

## 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]

| Property | Value |
|---|---|
| **Vendor** | Acumatica |
| **System** | Acumatica Cloud ERP 2024 R2 |
| **API Surface** | REST (Contract-Based) |
| **Current API Version** | Default endpoint 24.200.001 |
| **Editions Covered** | General Business, Distribution, Manufacturing, Construction, Retail Commerce |
| **Deployment** | Cloud (SaaS) / Private Cloud / On-Premise |
| **API Docs URL** | [Acumatica Help — Contract-Based REST API](https://help.acumatica.com/Help?ScreenId=ShowWiki&pageid=735fad82-9cf3-4a2c-8538-1c8344aba844) |
| **Status** | GA |

## 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 Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| Contract-Based REST API | HTTPS/JSON | Full CRUD, actions, file attachments | Configurable via $top (default varies) | License-tier-dependent (100-150/min) | Yes | Partial (paged) |
| OData (GI-Based) | HTTPS/JSON+OData | Reporting, BI tools (Power BI, Excel) | Configurable via $top | Shared with REST | Yes | Read-only |
| OData (DAC-Based) | HTTPS/JSON+OData | Direct table access, deleted record tracking | Configurable via $top | Shared with REST | Yes | Read-only |
| Screen-Based SOAP API | SOAP/XML | Legacy integrations (deprecated for new dev) | N/A | Shared | Yes | No |
| Push Notifications | HTTP callbacks | Real-time change detection | N/A | N/A | Yes | N/A |
| Webhooks | HTTP POST (inbound) | External system pushes data into Acumatica | N/A | N/A | Yes | N/A |

[src1, src4]

## Rate Limits & Quotas

### Per-Request Limits

| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per query page | Configurable via `$top` | REST API | Default page size varies by entity; use `$top` and `$skip` for pagination [src1] |
| Max request body size | Not officially published | REST API | Large file attachments may hit IIS/web server limits |
| Max `$expand` depth | 2 levels | REST API | Nested expansions beyond 2 levels return errors [src2] |
| Max batch operations | Not natively supported | REST API | Acumatica REST API does not have a native batch endpoint like OData $batch; use sequential calls [src1] |

[src1, src2]

### Rolling / Daily Limits

| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API requests per minute | ~100 (standard), ~150 (L-series) | Per minute | License-tier-dependent; configurable only by Acumatica support [src6, src7] |
| Concurrent API sessions | 3 (standard), 6 (L-series) | Per instance | Each unauthenticated request counts as a new session if cookies are not reused [src6] |
| Maximum web service API users | License-dependent | Per instance | Checked on System > License Monitoring Console (SM604000) [src1] |
| Session timeout | ~20 minutes idle | Per session | Cookie-based sessions expire; OAuth tokens depend on configuration [src2] |

[src6, src7]

## 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]

| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 Authorization Code | User-context web applications, interactive integrations | Configurable (default ~20 min session) | Yes (with offline_access scope) | Requires redirect URI; user authenticates via Acumatica login page [src1] |
| OAuth 2.0 Resource Owner Password | Server-to-server where OAuth auth code is not feasible | Configurable | Yes (with offline_access scope) | Sends username/password directly; less secure, use only when auth code flow is not possible [src1] |
| OAuth 2.0 Implicit | Single-page apps (SPAs) | Short-lived | No | Tokens exposed in URL fragment; not recommended for production [src1] |
| Cookie-Based Login | Simple integrations, quick prototyping | ~20 minutes idle timeout | No (re-login required) | POST to /entity/auth/login with credentials; must explicitly logout to free session [src2] |

[src1, src2, src3]

### Authentication Gotchas

- **OAuth `api:concurrent_access` scope and cookies**: When using this scope, Acumatica tracks sessions via cookies. If you do not pass the session cookie back with subsequent requests, each API call counts as a new concurrent user — quickly exhausting your license limit. [src6]
- **Cookie-based login requires explicit logout**: If you do not POST to `/entity/auth/logout` after completing your operations, the session remains open and counts against your concurrent session limit until it times out (~20 min). [src2, src6]
- **Client ID includes company ID**: The OAuth client ID generated by Acumatica includes the company/tenant identifier. The client application only has access to the data of the company specified in the client ID. [src1]
- **OAuth scopes**: Use `api` for standard access, `api:concurrent_access` for multi-session scenarios (with cookie management), and `api:offline_access` for refresh tokens. Combining scopes incorrectly can cause authentication failures. [src6]

## Constraints

- **License-tier rate limits cannot be self-adjusted** — you must contact Acumatica to increase the maximum requests per minute or concurrent sessions. There is no self-service throttle configuration. [src6, src7]
- **No native batch/bulk API** — unlike Salesforce Bulk API or NetSuite CSV import, Acumatica REST API processes records individually. For high-volume operations, you must implement client-side batching with pagination and rate limiting. [src1]
- **Endpoint version must match instance version** — calling `/entity/Default/24.200.001/` against an instance running 2023 R2 (23.200.001) returns 404. Always verify the instance version before building endpoint URLs. [src2]
- **Field value wrapping is mandatory** — all field values must be sent as `{"value": "..."}` objects, not flat strings. This applies to creates, updates, and action parameters. [src2]
- **Custom endpoint extensions are instance-specific** — if you extend the Default endpoint with custom fields or entities, those extensions exist only on that specific instance. They must be manually recreated or exported/imported via customization packages when migrating between environments. [src1]
- **Screen-based SOAP API is deprecated** — Acumatica recommends contract-based REST for all new development. Screen-based SOAP is maintained for legacy compatibility only. [src3, src4]

## Integration Pattern Decision Tree

```
START — User needs to integrate with Acumatica ERP
|-- What's the integration pattern?
|   |-- Real-time (individual records, <1s)
|   |   |-- Data volume < 100 records/operation?
|   |   |   |-- YES --> Contract-Based REST API: CRUD endpoints [src1]
|   |   |   |-- NO --> REST API with client-side chunking + rate limiting
|   |   |-- Need to execute business logic (release, approve)?
|   |   |   |-- YES --> REST API actions (POST /entity/{Entity}/{ID}/{Action}) [src1]
|   |   |   |-- NO --> Standard CRUD endpoints
|   |   |-- Need real-time change notifications?
|   |       |-- YES --> Push Notifications (SM302000) [src1]
|   |       |-- NO --> Polling with $filter on LastModifiedDateTime
|   |-- Batch/Bulk (scheduled, high volume)
|   |   |-- > 100,000 records/day?
|   |   |   |-- YES --> Consider Acumatica Import Scenarios or file-based approach
|   |   |   |-- NO --> REST API with client-side batching ($top/$skip pagination)
|   |   |-- Need delta sync?
|   |       |-- YES --> Filter by LastModifiedDateTime [src1]
|   |       |-- NO --> Full extract with pagination
|   |-- Event-driven (webhooks, push notifications)
|   |   |-- External system pushes data TO Acumatica?
|   |   |   |-- YES --> Webhooks (SM304000) — map external format to Acumatica entities [src1]
|   |   |   |-- NO --> Push Notifications for outbound change events [src1]
|   |-- File-based (CSV/XML import)
|       |-- Use Acumatica Import Scenarios (built-in) for CSV/Excel
|       |-- Use REST API for programmatic file attachment
|-- Which direction?
|   |-- Inbound (writing to Acumatica) --> POST/PUT with rate limiting
|   |-- Outbound (reading from Acumatica) --> GET with $filter + pagination
|   |-- Bidirectional --> Design conflict resolution + use LastModifiedDateTime
|-- Error tolerance?
    |-- Zero-loss --> Implement idempotency checks (query before insert) + logging
    |-- Best-effort --> Fire-and-forget with retry on 4xx/5xx
```

## Quick Reference

| Operation | Method | Endpoint | Payload | Notes |
|---|---|---|---|---|
| Get all records | GET | `/entity/Default/24.200.001/{Entity}` | N/A | Use $top and $skip for pagination [src1] |
| Get single record | GET | `/entity/Default/24.200.001/{Entity}/{ID}` | N/A | ID is the internal GUID [src1] |
| Get by key fields | GET | `/entity/Default/24.200.001/{Entity}/{KeyValue}` | N/A | e.g., `/SalesOrder/SO/000042` (OrderType/OrderNbr) [src2] |
| Create record | PUT | `/entity/Default/24.200.001/{Entity}` | JSON | Fields wrapped in `{"value": "..."}` [src2] |
| Update record | PUT | `/entity/Default/24.200.001/{Entity}` | JSON | Include key fields + changed fields [src2] |
| Delete record | DELETE | `/entity/Default/24.200.001/{Entity}/{ID}` | N/A | Not all entities support delete [src2] |
| Execute action | POST | `/entity/Default/24.200.001/{Entity}/{ID}/action/{ActionName}` | JSON (action params) | e.g., ReleaseDocument, ProcessPayment [src1] |
| Filter records | GET | `/entity/Default/24.200.001/{Entity}?$filter=...` | N/A | OData-style: eq, gt, lt, ge, le, contains [src1, src2] |
| Expand related | GET | `/entity/Default/24.200.001/{Entity}?$expand=Details` | N/A | Max 2 levels deep [src2] |
| Attach file | PUT | `/entity/Default/24.200.001/{Entity}/{ID}/files/{filename}` | Binary | Content-Type matches file type [src1] |
| Login (cookie) | POST | `/entity/auth/login` | JSON credentials | Returns session cookie [src2] |
| Logout (cookie) | POST | `/entity/auth/logout` | N/A | Always call to free session [src2] |

## 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 (Authorization Code or Resource Owner Password), and configure the redirect URI if using auth code flow. Copy the generated Client ID and create a shared secret. [src1]

```plaintext
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]

```bash
# Input:  Acumatica instance URL, client_id, client_secret, username, password
# Output: Access token for REST API calls

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"

# Response:
# {
#   "access_token": "eyJ...",
#   "expires_in": 3600,
#   "token_type": "Bearer",
#   "scope": "api"
# }
```

**Verify**: The response contains an `access_token` field. Use it in subsequent requests.

### 3. Retrieve records with filtering and pagination

Use GET with OData-style query parameters to retrieve records. Implement pagination using `$top` and `$skip`. [src1, src2]

```bash
# Input:  Access token, entity name
# Output: JSON array of matching records

# Get first 50 sales orders with status "Open"
curl -X GET "https://yourinstance.acumatica.com/entity/Default/24.200.001/SalesOrder?\$filter=Status%20eq%20'Open'&\$top=50&\$skip=0&\$expand=Details" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"

# Next page:
curl -X GET "https://yourinstance.acumatica.com/entity/Default/24.200.001/SalesOrder?\$filter=Status%20eq%20'Open'&\$top=50&\$skip=50" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
```

**Verify**: Response is a JSON array. If fewer than `$top` records are returned, you have reached the last page.

### 4. Create and update records

Use PUT to create or update records. Include key fields for updates; omit the ID field for creates. All field values must be wrapped in `{"value": "..."}` objects. [src2]

```bash
# Input:  Access token, entity data
# Output: Created/updated record with system-generated fields

# Create a new customer
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"},
    "Status": {"value": "Active"}
  }'

# Update existing customer
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": "Updated Customer Name LLC"}
  }'
```

**Verify**: Response returns the full record with all fields populated, including system-generated fields like `LastModifiedDateTime`.

### 5. Execute business actions

Use POST to invoke business actions on records, such as releasing invoices or processing payments. [src1]

```bash
# Input:  Access token, record key, action name
# Output: Action result (often 204 No Content on success)

# Release an invoice
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 '{}'

# For actions with parameters:
curl -X POST "https://yourinstance.acumatica.com/entity/Default/24.200.001/SalesOrder/SO/000042/action/CreateShipment" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "entity": {
      "OrderType": {"value": "SO"},
      "OrderNbr": {"value": "000042"}
    }
  }'
```

**Verify**: GET the record again and confirm the status has changed (e.g., from "Balanced" to "Released").

### 6. Implement error handling and rate limit retries

Parse nested error details from Acumatica responses. Implement exponential backoff for rate limit (429) responses. [src5, src6]

```python
# Input:  Acumatica instance URL, access token, request parameters
# Output: Successful response or structured error information

import requests
import time
import json

def acumatica_request(method, url, token, data=None, max_retries=5):
    """Make an Acumatica API request with retry logic."""
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
        "Accept": "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:
            # Rate limited — exponential backoff
            wait = min(2 ** attempt * 2, 60)
            print(f"Rate limited. Waiting {wait}s (attempt {attempt + 1})")
            time.sleep(wait)
            continue

        if response.status_code == 401:
            raise Exception("Authentication failed — token expired or invalid")

        if response.status_code == 400:
            # Parse nested error details
            error_body = response.json()
            error_msg = error_body.get("message", "Unknown error")
            details = error_body.get("exceptionMessage", "")
            raise Exception(f"Validation error: {error_msg}. Details: {details}")

        if response.status_code == 404:
            raise Exception(
                "Entity or endpoint not found — verify endpoint version "
                "matches instance version and entity name capitalization"
            )

        raise Exception(f"API error {response.status_code}: {response.text}")

    raise Exception("Max retries exceeded — rate limit not cleared")
```

**Verify**: Test with an invalid field to confirm error parsing returns a readable message.

## Code Examples

### Python: Retrieve and paginate all sales orders

```python
# 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):
    """Paginate through all sales orders matching a status filter."""
    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}'"
            f"&$top={page_size}&$skip={skip}"
            f"&$select=OrderType,OrderNbr,CustomerID,OrderTotal,Status"
        )
        resp = requests.get(url, headers=headers)
        resp.raise_for_status()
        page = resp.json()

        if not page:
            break

        all_orders.extend(page)
        if len(page) < page_size:
            break  # Last page
        skip += page_size

    return all_orders

# Usage:
# orders = get_all_sales_orders("https://myinstance.acumatica.com", token)
# print(f"Total orders: {len(orders)}")
```

### JavaScript/Node.js: Create a customer with error handling

```javascript
// Input:  Acumatica instance URL, access token, customer data
// Output: Created customer record or error details

// npm install axios@1.x
const axios = require("axios");

async function createCustomer(instanceUrl, token, customerData) {
  const url = `${instanceUrl}/entity/Default/24.200.001/Customer`;

  // Wrap field values in Acumatica's required format
  const payload = {};
  for (const [key, val] of Object.entries(customerData)) {
    payload[key] = { value: val };
  }

  try {
    const response = await axios.put(url, payload, {
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
        Accept: "application/json",
      },
    });
    console.log("Customer created:", response.data.CustomerID.value);
    return response.data;
  } catch (error) {
    if (error.response?.status === 400) {
      const msg = error.response.data?.message || "Validation error";
      console.error("Validation failed:", msg);
    } else if (error.response?.status === 429) {
      console.error("Rate limited — implement backoff");
    }
    throw error;
  }
}

// Usage:
// createCustomer("https://myinstance.acumatica.com", token, {
//   CustomerID: "NEWCUST01",
//   CustomerName: "My New Customer",
//   CustomerClass: "DEFAULT"
// });
```

### cURL: Quick API connectivity test

```bash
# Input:  Acumatica instance URL, username, password
# Output: Session cookie and stock item list

# Option 1: 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 -X GET \
  "https://yourinstance.acumatica.com/entity/Default/24.200.001/StockItem?\$top=5" \
  -H "Accept: application/json"

# Always logout to free session
curl -b cookies.txt -X POST \
  "https://yourinstance.acumatica.com/entity/auth/logout"

# Option 2: OAuth token-based (preferred)
TOKEN=$(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" \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")

curl -s "https://yourinstance.acumatica.com/entity/Default/24.200.001/StockItem?\$top=5" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
```

## Data Mapping

### Field Mapping Reference

| Acumatica Field Pattern | API Representation | Type | Transform | Gotcha |
|---|---|---|---|---|
| CustomerID | `{"CustomerID": {"value": "CUST01"}}` | String (key field) | Wrapped in value object | Must match exact case and format configured in Acumatica [src2] |
| OrderTotal | `{"OrderTotal": {"value": 1500.00}}` | Decimal | Wrapped in value object | Currency decimals depend on currency configuration [src2] |
| OrderDate | `{"OrderDate": {"value": "2026-03-02T00:00:00"}}` | DateTime | ISO 8601 format | Time zone depends on user/branch configuration [src1] |
| Status | `{"Status": {"value": "Open"}}` | String (enum) | Use display value, not internal code | Available values vary by entity and customization [src2] |
| Detail lines | `{"Details": [{"InventoryID": {"value": "ITEM01"}, ...}]}` | Array | Nested value objects | Child records follow same wrapping pattern [src2] |
| Custom fields | `{"custom": {"FieldName": {"value": "..."}}}` | Varies | Wrapped under `custom` key | Custom fields from customization projects use a different path [src1] |
| NoteID (GUID) | `{"id": "a1b2c3d4-..."}` | GUID | Not wrapped in value object | System-generated; used for single-record GET by ID [src2] |

### Data Type Gotchas

- **DateTime values and time zones**: Acumatica stores dates in the branch's configured time zone. The REST API returns dates in ISO 8601 format, but the time zone offset may not be included. Always confirm the branch time zone setting before assuming UTC. [src1]
- **Decimal precision**: Currency fields follow the currency's decimal precision configuration. Sending more decimal places than configured may cause silent rounding or validation errors. [src2]
- **Entity name capitalization matters**: Entity names in endpoint URLs are case-sensitive. `SalesOrder` works; `salesorder` or `SALESORDER` returns 404. [src2]
- **Multi-select fields**: Acumatica handles multi-select attributes differently than single-value fields. Check the entity schema for the correct format before sending array values. [src1]

## Error Handling & Failure Points

### Common Error Codes

| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 400 Bad Request | Validation error | Missing required field, wrong value format, or field not writable | Parse nested error details for field-level validation messages [src5] |
| 401 Unauthorized | Authentication failure | Expired token, invalid credentials, or session timeout | Re-authenticate; for OAuth, request a new token [src2] |
| 403 Forbidden | Insufficient permissions | API user lacks access rights to the entity or field | Check user role permissions in Acumatica security configuration |
| 404 Not Found | Entity or endpoint not found | Wrong endpoint version, misspelled entity name, or record does not exist | Verify endpoint version matches instance; check entity name capitalization [src2] |
| 429 Too Many Requests | Rate limit exceeded | More requests/min than license tier allows | Implement exponential backoff; consider upgrading license tier [src6, src7] |
| 500 Internal Server Error | Server-side error | Business logic exception, database constraint violation, or customization bug | Check Acumatica system trace for detailed error; may require support ticket |

[src2, src5, src6]

### Failure Points in Production

- **Session exhaustion from missing logout calls**: Cookie-based integrations that crash or fail without calling `/entity/auth/logout` leave orphaned sessions. With only 3-6 concurrent sessions available, this can lock out all API access until sessions time out (~20 min). Fix: `Always wrap API calls in try/finally with logout in the finally block. Use OAuth tokens instead of cookie sessions when possible.` [src2, src6]
- **Rate limit exhaustion in eCommerce scenarios**: Real-time pricing lookups for eCommerce storefronts can easily exceed 100 requests/min, especially when each product requires multiple API calls for pricing, inventory, and availability. Fix: `Implement a caching layer (Redis/Memcached) for frequently accessed data. Batch price lookups where possible. Consider upgrading to L-series license.` [src7]
- **Endpoint version mismatch after Acumatica upgrade**: When Acumatica is upgraded from 2023 R2 to 2024 R2, existing integrations hardcoded to `23.200.001` continue to work (backward compatibility), but new entities/fields added in 24.200.001 are not available on the old endpoint version. Fix: `Store the endpoint version in configuration, not hardcoded. Test integrations in a sandbox after each Acumatica upgrade.` [src2, src8]
- **Silent data truncation on oversized field values**: Sending a string longer than the field's maximum length may silently truncate rather than returning an error, depending on the entity configuration. Fix: `Query the entity metadata to check field lengths before sending data. Validate client-side.` [src5]
- **Push notification delivery failures go unnoticed**: Push notifications are fire-and-forget — if the receiving endpoint is down, the notification is lost. There is no built-in retry or dead letter queue. Fix: `Implement a heartbeat check on the notification endpoint. Use polling with LastModifiedDateTime as a fallback to catch missed notifications.` [src1]

## Anti-Patterns

### Wrong: Sending flat field values without wrapper

```json
// BAD — Acumatica requires {"value": "..."} wrapping
{
  "CustomerID": "CUST01",
  "CustomerName": "My Customer"
}
```

### Correct: Wrap every field value in a value object

```json
// GOOD — Proper Acumatica REST API field format
{
  "CustomerID": {"value": "CUST01"},
  "CustomerName": {"value": "My Customer"}
}
```

### Wrong: Not logging out of cookie-based sessions

```python
# 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

```python
# 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 in source code

```python
# BAD — breaks when Acumatica is upgraded or version mismatches
endpoint = f"{instance}/entity/Default/24.200.001/SalesOrder"
```

### Correct: Make endpoint version configurable

```python
# GOOD — version stored in configuration, easy to update
import os
API_VERSION = os.environ.get("ACUMATICA_API_VERSION", "24.200.001")
endpoint = f"{instance}/entity/Default/{API_VERSION}/SalesOrder"
```

## Common Pitfalls

- **Using `api:concurrent_access` without cookie management**: This OAuth scope tells Acumatica to track concurrent sessions via cookies. If you do not persist and return cookies with each request, every API call is treated as a new session, rapidly hitting the concurrent session limit. Fix: `Either use the plain 'api' scope (no concurrency tracking) or implement proper cookie jar management in your HTTP client.` [src6]
- **Ignoring pagination and assuming all records are returned**: The REST API returns a default page of records; it does not return all matching records in a single response. Large datasets silently stop at the page boundary. Fix: `Always implement pagination with $top and $skip. Continue fetching until the returned page is smaller than $top.` [src1, src2]
- **Not verifying endpoint version before deployment**: Deploying an integration built against `24.200.001` to a customer still on `23.200.001` returns 404 errors for all API calls. Fix: `Query the instance version first (GET /) and dynamically construct endpoint URLs. Store the version in configuration.` [src2]
- **Treating Acumatica REST API like a standard OData service**: While Acumatica uses OData-style query parameters ($filter, $top, $skip, $expand), it is not a fully OData-compliant service. Features like $count, $search, complex $filter expressions, and $batch are not supported. Fix: `Test each OData feature against your Acumatica instance before relying on it. Consult the contract-based API documentation for supported query parameters.` [src1]
- **Not handling the business date header**: Some operations (e.g., financial transactions) use the `PX-CbApiBusinessDate` header to determine which financial period a transaction falls into. Omitting this header uses the server's current date, which may not match your intended posting date. Fix: `Set the PX-CbApiBusinessDate header explicitly for financial operations.` [src1]
- **Creating records with PUT instead of expecting POST**: Acumatica uses PUT for both create and update operations (not POST for create). The presence or absence of key fields determines whether it's a create or update. Fix: `Use PUT for all create/update operations. Include key fields for updates; omit system-generated IDs for creates.` [src2]

## Diagnostic Commands

```bash
# Check Acumatica instance version and available endpoints
curl -s "https://yourinstance.acumatica.com/entity/Default/24.200.001" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"

# Test authentication (OAuth token)
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" \
  | python3 -c "import sys,json;d=json.load(sys.stdin);print('Token OK' if 'access_token' in d else f'Error: {d}')"

# List available entities on an endpoint
curl -s "https://yourinstance.acumatica.com/entity/Default/24.200.001" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" | 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" \
  -H "Accept: application/json" | python3 -m json.tool

# Test cookie-based login and session health
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"}' && echo "Login OK"

# Check license limits (requires UI access)
# Navigate to: System > Licensing > License Monitoring Console (SM604000)
# Or via API (if accessible): GET /entity/Default/24.200.001/LicenseMonitoringConsole
```

## Version History & Compatibility

| Endpoint Version | Acumatica Release | Status | Key Changes | Migration Notes |
|---|---|---|---|---|
| 24.200.001 | 2024 R2 | Current | New OData URLs; OpenAPI 3.0 support; deleted record tracking via DAC OData | Legacy OData URLs deprecated, sunset in 2025 R2 [src8] |
| 23.200.001 | 2023 R2 | Supported | Additional manufacturing and distribution entities | Same as 2024 R1 endpoint version |
| 22.200.001 | 2022 R2 | Supported | DAC Schema Browser introduced (2021 R2); expanded default entities | Minimum version for many ISV integrations |
| 20.200.001 | 2020 R2 | Legacy | Push notifications and webhooks matured | Consider upgrading — limited support |
| 18.200.001 | 2018 R2 | EOL | Early contract-based REST API | Upgrade required — no longer supported |

[src1, src4, src8]

### Deprecation Policy

Acumatica supports previous endpoint versions for backward compatibility — calling an older endpoint version (e.g., `22.200.001`) on a newer instance (2024 R2) 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. The legacy OData URL format is being sunset in 2025 R2, giving integrators one full release cycle to migrate to the new URLs. [src1, src8]

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Full CRUD operations on Acumatica business entities (customers, orders, invoices) | Read-only reporting or BI dashboards | OData interface (GI-based or DAC-based) for Power BI/Excel [src1] |
| Executing business actions (release, approve, process) through API | Bulk data migration of >100K records in a single batch | Acumatica Import Scenarios (built-in CSV/Excel import) |
| Building real-time integrations with eCommerce, CRM, or other systems | Simple field-level customization without external integration | Acumatica Customization Framework (no API needed) |
| Server-to-server integration requiring stable versioned contracts | Direct database access or SQL queries | N/A — never use direct DB access with Acumatica Cloud |
| Attaching files and documents to Acumatica records programmatically | Legacy screen-scraping integrations | Contract-based REST API replaces screen-based SOAP [src4] |

## Important Caveats

- Rate limits and concurrent session limits are entirely determined by your Acumatica license tier and cannot be self-configured. For high-throughput scenarios (eCommerce, warehouse automation), verify your license tier supports the required request volume before development. [src6, src7]
- The contract-based REST API uses PUT for both create and update operations — this is non-standard REST behavior. POST is reserved for executing actions on existing records, not for creating new records. [src2]
- Custom endpoint extensions (adding custom fields to the Default endpoint) are instance-specific. When migrating between environments (dev to staging to production), you must include the endpoint customization in your Acumatica customization package. [src1]
- Acumatica's multi-tenant architecture means API limits are per-tenant, not per-instance. If multiple integrations share the same tenant, they share the same rate limit pool. [src6]
- Field values in API responses may differ from UI display values. For example, status fields may show internal codes rather than display labels depending on the endpoint configuration. Always test with real data before assuming field value formats. [src2, src5]

## Related Units

- [Acumatica OAuth 2.0 and cookie-based authentication flows](/business/erp-integration/acumatica-authentication/2026)
- [NetSuite REST API for mid-market ERP integration](/business/erp-integration/netsuite-rest-api/2026)
- [NetSuite to Acumatica migration playbook](/business/erp-integration/netsuite-acumatica-migration/2026)
