This card covers Acumatica ERP's screen-based SOAP web services API and compares it with the contract-based REST API. The screen-based API has been available since Acumatica's earliest versions and provides direct programmatic access to every form in the system. The contract-based REST API was introduced in Acumatica 5.3 (2016) as a more stable, decoupled alternative. Both APIs are available in all Acumatica editions across cloud, hosted, and on-premise deployments. This card does NOT cover OData-based data retrieval.
| Property | Value |
|---|---|
| Vendor | Acumatica |
| System | Acumatica ERP 2024 R2 (24.200) |
| API Surface | SOAP (Screen-Based) + REST (Contract-Based) + OData |
| Current API Version | Screen-Based: per-screen WSDL; REST: 24.200.001 |
| Editions Covered | Small Business, Standard, Enterprise |
| Deployment | Cloud / Hosted / On-Premise |
| API Docs | Acumatica Integration Development Guide |
| Status | Screen-Based: Maintenance; REST: GA (actively developed) |
Acumatica provides three distinct API surfaces, each with different strengths and trade-offs. The screen-based API offers the broadest functional coverage but the least stability; the contract-based REST API offers stability and ease of use but may not expose every screen action. [src1, src3]
| API Surface | Protocol | Best For | Stability | Coverage | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| Screen-Based SOAP | HTTPS/XML (SOAP) | Automating any screen action, processing screens, legacy | Low - breaks on UI changes | Complete (every form) | Yes | Via Export/Import |
| Contract-Based REST | HTTPS/JSON (REST) | CRUD operations, new integrations, cross-platform | High - decoupled from UI | Partial (configured endpoints) | Yes | Limited |
| Contract-Based SOAP | HTTPS/XML (SOAP) | Legacy contract-based integrations | High - decoupled from UI | Same as REST | Yes | Limited |
| OData (GI-Based) | HTTPS/JSON (OData v4) | BI tools, reporting, read-only | High | Generic Inquiries only | Yes | Read-only |
| OData (DAC-Based) | HTTPS/JSON (OData v4) | Direct DAC access for reporting | High | DAC-exposed entities | Yes | Read-only |
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max concurrent API user sessions | License-dependent | All API surfaces | Configured on SM604000; exceeding returns login error |
| Request body size | No published hard limit | REST API | Denial Code 511 returned for oversized payloads |
| Export batch size | Configurable per call | Screen-Based Export() | Set via TopCount parameter |
| Composite batch size | 1,000 per $batch | REST API | Each subrequest counts as a separate API call |
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API requests per minute | License-dependent | Per minute | At 50% of limit, requests are throttled; at 100%, rejected |
| Concurrent API users | License-dependent | Concurrent sessions | Small Business: fewer; Enterprise: more. Check SM604000 |
| Total API calls | No published daily cap | 24h | Governed by per-minute rate limit and concurrent user limit |
Acumatica supports multiple authentication methods across its API surfaces. OAuth 2.0 is recommended for all new integrations. The screen-based API historically used cookie-based authentication via Login/Logout SOAP methods, but OAuth 2.0 tokens can also be used. [src3, src7]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 Authorization Code | Interactive applications, user-context | Configurable | Yes | Register on SM303010 (Connected Applications) |
| OAuth 2.0 Resource Owner Password | Server-to-server integrations | Configurable | Yes | Simpler setup but less secure |
| Cookie-based (Login/Logout SOAP) | Legacy screen-based API integrations | Session-based | No | Uses Login() method with username, password, company, branch |
| OAuth 2.0 Client Credentials | Service-to-service, no user context | Configurable | Yes | Available in newer Acumatica versions |
START - Need to integrate with Acumatica ERP
|-- Is this a new integration or maintaining an existing one?
| |-- NEW integration
| | |-- Do you need to automate a screen action not exposed via REST?
| | | |-- YES --> Screen-Based SOAP API (only option)
| | | |-- NO --> Contract-Based REST API (recommended)
| | |-- Do you need read-only data for BI/reporting?
| | | |-- YES --> OData (GI-based or DAC-based)
| | | |-- NO --> REST API
| | |-- What client environment?
| | |-- .NET --> REST API or Screen-Based with PX.Soap.dll
| | |-- Non-.NET (Python, JS, Java) --> REST API (no wrapper)
| |-- EXISTING screen-based integration
| |-- Is it working and stable?
| | |-- YES --> Keep it, plan migration to REST over time
| | |-- NO (breaking on upgrades) --> Migrate to REST now
| |-- Can all operations be replicated with REST?
| |-- YES --> Migrate to REST API
| |-- NO --> Keep screen-based for those ops, REST for rest
|-- Data flow direction?
| |-- Read/Export --> REST API GET or Screen-Based Export()
| |-- Write/Import --> REST API PUT/POST or Screen-Based Import()
| |-- React to changes --> Push Notifications or Webhooks
|-- Error handling?
|-- Zero-loss required --> Idempotency + GetProcessStatus() polling
|-- Best-effort --> REST API with retry on 429/500
| Operation | Screen-Based API | Contract-Based REST API | Notes |
|---|---|---|---|
| Endpoint URL | {site}/Soap/{FormID}.asmx | {site}/entity/{EndpointName}/{Version}/{Entity} | Screen-based uses form IDs (e.g., AR301000.asmx) |
| WSDL | {site}/Soap/{FormID}.asmx?WSDL | N/A (OpenAPI via SM207000) | Screen-based generates WSDL per form |
| Create record | Submit() with Content object | PUT /entity/Default/24.200.001/{Entity} | REST uses JSON; screen-based uses SOAP XML |
| Read records | Export() with filters | GET /entity/Default/24.200.001/{Entity}?$filter=... | REST supports OData query syntax |
| Update record | Submit() with modified Content | PUT /entity/Default/24.200.001/{Entity} | Both use record key fields |
| Delete record | Clear() then Submit() | DELETE /entity/Default/24.200.001/{Entity}/{keys} | Screen-based requires clearing then submitting |
| Execute action | Submit() with action trigger | POST .../SalesOrder/ReleaseOrder | Screen-based can trigger any button; REST only configured actions |
| Check async status | GetProcessStatus() | GET .../{Entity}?$longRunning=true | Screen-based has dedicated polling method |
| Login | Login(name, password, company, branch) | OAuth 2.0 Bearer token | Screen-based: session cookie; REST: Authorization header |
| Logout | Logout() | N/A (token expiry) | Screen-based MUST call Logout() to free session |
| Schema discovery | GetSchema() / SetSchema() | /entity/Default/24.200.001 | Screen-based caches schema locally via PX.Soap.dll |
Evaluate whether the integration requires screen-specific functionality. Only fall back to screen-based API when you need access to processing screens, custom actions, or form-specific logic not exposed through contract-based endpoints. [src1]
Decision checklist:
- [ ] Can the operation be done via REST API endpoint? --> Use REST
- [ ] Does it require a specific screen action? --> Check REST endpoint first
- [ ] Is the client .NET? --> Screen-based option available
- [ ] Is the client non-.NET? --> Must use REST API
- [ ] Is this a greenfield integration? --> Use REST API
Verify: Check REST endpoint configuration on SM207000 to see if your required entities and actions are exposed.
Register your application on SM303010 (Connected Applications) for OAuth 2.0. [src3, src7]
# Obtain access token (Resource Owner Password flow)
curl -X POST "https://your-instance.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=YOUR_PASSWORD" \
-d "scope=api"
Verify: GET /entity/Default/24.200.001/ returns a list of available entities.
For screen-based SOAP integrations, use the Login() method via PX.Soap.dll wrapper. [src4]
// Create SOAP client for Sales Order screen (SO301000)
var soapClient = new SO301000Screen();
soapClient.Url = url + "/Soap/SO301000.asmx";
soapClient.CookieContainer = new System.Net.CookieContainer();
soapClient.Login(username, password, company, null, null);
// Always call soapClient.Logout() when done
Verify: A successful Login() call returns without error.
Use standard REST methods for creating, reading, updating, and deleting records. [src3, src7]
# Query stock items with filter
curl -X GET "https://your-instance.acumatica.com/entity/Default/24.200.001/StockItem?\$filter=InventoryID eq 'AALEGO500'" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"
# Create a stock item (PUT for upsert)
curl -X PUT "https://your-instance.acumatica.com/entity/Default/24.200.001/StockItem" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"InventoryID":{"value":"NEWITEM001"},"Description":{"value":"New Test Item"},"ItemClass":{"value":"STOCKITEM"}}'
Verify: GET query with filter returns the created record.
Use for operations that require specific screen actions not exposed through REST endpoints. [src4]
var schema = PX.Soap.Helper.GetSchema<SO301000Content>(soapClient);
var commands = new SO301000Command[]
{
schema.Actions.Insert,
new SO301000Value { Value = "SO", LinkedCommand = schema.OrderSummary.OrderType },
new SO301000Value { Value = "ABARTENDE", LinkedCommand = schema.OrderSummary.CustomerID },
schema.Actions.Save
};
var result = soapClient.Submit(commands);
soapClient.Logout();
Verify: Log into Acumatica UI and confirm the new order appears on SO301000.
# Input: Acumatica instance URL, OAuth credentials
# Output: Stock item created and queried
import requests
base_url = "https://your-instance.acumatica.com"
# Authenticate via OAuth 2.0
token_resp = requests.post(
f"{base_url}/identity/connect/token",
data={
"grant_type": "password",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_SECRET",
"username": "admin",
"password": "YOUR_PASS",
"scope": "api"
}
)
token = token_resp.json()["access_token"]
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
# Create a stock item (PUT for upsert)
item = {
"InventoryID": {"value": "PYITEM001"},
"Description": {"value": "Python-created item"},
"ItemClass": {"value": "STOCKITEM"}
}
resp = requests.put(f"{base_url}/entity/Default/24.200.001/StockItem", headers=headers, json=item)
print(f"Create: {resp.status_code}")
// Input: Authenticated screen-based SOAP client
// Output: Exported stock items from Inventory screen
var client = new IN202500Screen();
client.Url = "https://your-instance.acumatica.com/Soap/IN202500.asmx";
client.CookieContainer = new System.Net.CookieContainer();
client.Login("admin", "password", "MyCompany", null, null);
try {
var schema = Helper.GetSchema<IN202500Content>(client);
var exportCommands = new IN202500Command[] {
schema.StockItemSummary.InventoryID,
schema.StockItemSummary.Description,
schema.StockItemSummary.ItemStatus
};
var result = client.Export(exportCommands, null, 0, false, false);
foreach (var row in result)
Console.WriteLine($"ID: {row[0]}, Desc: {row[1]}, Status: {row[2]}");
} finally {
client.Logout();
}
# Input: Acumatica instance URL, credentials
# Output: Token + entity list
# Get OAuth token
TOKEN=$(curl -s -X POST \
"https://your-instance.acumatica.com/identity/connect/token" \
-d "grant_type=password&client_id=YOUR_ID&client_secret=YOUR_SECRET&username=admin&password=YOUR_PASS&scope=api" \
| jq -r '.access_token')
# List available entities
curl -s "https://your-instance.acumatica.com/entity/Default/24.200.001/" \
-H "Authorization: Bearer $TOKEN" | jq '.[].name'
| Screen-Based Field Path | REST API Field | Type | Transform | Gotcha |
|---|---|---|---|---|
| OrderSummary.OrderType | OrderType.value | String | Direct | REST wraps values in {value: ...} objects |
| OrderSummary.CustomerID | CustomerID.value | String | Direct | Must match exact customer ID format |
| DocumentDetails.InventoryID | Details[].InventoryID.value | String | Array nesting differs | Screen-based uses flat commands; REST uses nested JSON arrays |
| OrderSummary.OrderTotal | OrderTotal.value | Decimal | Direct | Read-only in both APIs (calculated field) |
| Actions.ReleaseOrder | POST .../SalesOrder/ReleaseOrder | Action | Different invocation | Screen-based: include in Submit() commands; REST: separate POST |
| RowNumber (Export) | $skip / $top | Pagination | Different mechanisms | Screen-based returns all rows; REST uses OData pagination |
{"value": actual_value}. Failing to wrap values returns validation errors. [src3]| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 429 | Rate limit exceeded | Too many API requests per minute | Exponential backoff; check SM604000 for rate limit |
| 401 | Unauthorized | Expired/invalid OAuth token or failed Login() | Re-authenticate; for screen-based, call Login() again |
| 403 | Forbidden | User lacks permissions | Check API user roles and permissions |
| 500 | Internal Server Error | Business logic validation failure or server error | Check Acumatica trace log; common: missing required field |
| 511 | Data size limit exceeded | Response payload too large | Add $filter and $top to reduce result set |
| Login limit | API user session limit reached | All licensed API user slots in use | Call Logout() on idle sessions; increase license count |
Wrap all screen-based API calls in try/finally blocks. Always call Logout() in finally. [src4, src6]Delete locally cached schema files after each Acumatica upgrade. [src4]Always use latest endpoint version. Update version number after each Acumatica upgrade. [src2, src7]Set HTTP client timeouts higher. Implement retry with exponential backoff. [src6]Add the field to endpoint mapping on SM207000. [src7]// BAD - screen-based API for simple CRUD is fragile and verbose
var client = new AR303000Screen();
client.Login(user, pass, company, null, null);
var schema = Helper.GetSchema<AR303000Content>(client);
// Breaks if AR303000 form layout changes in next Acumatica version
# GOOD - REST API is decoupled from UI, stable across versions
resp = requests.get(
f"{base_url}/entity/Default/24.200.001/Customer?$top=100",
headers={"Authorization": f"Bearer {token}"}
)
// BAD - session leak exhausts API user license slots
var client = new SO301000Screen();
client.Login(user, pass, company, null, null);
var result = client.Export(commands, null, 0, false, false);
// No Logout() - session stays open until timeout!
// GOOD - guaranteed session release
var client = new SO301000Screen();
client.Login(user, pass, company, null, null);
try {
var result = client.Export(commands, null, 0, false, false);
} finally {
client.Logout();
}
# BAD - version 17.200.001 was removed in 2023 R2
resp = requests.get(f"{base_url}/entity/Default/17.200.001/Customer")
# GOOD - centralized, easy to update on upgrade
API_VERSION = "24.200.001"
resp = requests.get(f"{base_url}/entity/Default/{API_VERSION}/Customer", headers=headers)
Check which SOAP type your integration uses. /entity/ URLs = contract-based (migrate to REST). /Soap/{FormID}.asmx = screen-based (still supported). [src2]Review SM207000 before starting REST development. Create custom endpoints for non-standard entities. [src7]Configure Push Notifications on SM302000 to trigger webhooks on record changes. [src3]Always set TopCount parameter and implement pagination. [src4]Test with the actual API user account in staging. [src6]{"value": "data"} objects, not bare values. Fix: Define proper DTOs or use Acumatica's official REST client from GitHub. [src3]# Check available REST API endpoints and versions
curl -s "https://your-instance.acumatica.com/entity/" \
-H "Authorization: Bearer $TOKEN" | jq .
# List all entities on the Default endpoint
curl -s "https://your-instance.acumatica.com/entity/Default/24.200.001/" \
-H "Authorization: Bearer $TOKEN" | jq '.[].name'
# Test authentication
curl -s -o /dev/null -w "%{http_code}" \
"https://your-instance.acumatica.com/entity/Default/24.200.001/" \
-H "Authorization: Bearer $TOKEN"
# Check screen-based WSDL availability
curl -s "https://your-instance.acumatica.com/Soap/SO301000.asmx?WSDL" | head -20
# Check API user licensing: navigate to SM604000 in Acumatica UI
# Review: Maximum Web Services API Users
# Review: Current API User Sessions
| API Version / Release | Release Date | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| 2024 R2 (24.200) | 2024-10 | Current | New OData URLs introduced | Legacy OData URLs supported until 2025 R2 |
| 2024 R1 (24.100) | 2024-04 | Supported | None | -- |
| 2023 R2 (23.200) | 2023-10 | Supported | Contract-based SOAP removed | Migrate to REST. Screen-based SOAP not affected. |
| 2023 R1 (23.100) | 2023-04 | Supported | None | Last version with contract-based SOAP |
| 2021 R2 (21.200) | 2021-08 | EOL | ODBC removed | Use OData or REST API instead |
| 5.3 (2016) | 2016 | EOL | N/A | Contract-based API first introduced |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Need to automate a processing screen action not exposed via REST | Building a new standard CRUD integration | Contract-based REST API |
| Maintaining an existing screen-based integration that works | Starting a greenfield integration project | Contract-based REST API |
| Need to interact with every field on a form as a user would | Only need to read/write business objects | Contract-based REST API or OData |
| Client is .NET and can use PX.Soap.dll wrapper | Client is Python, JavaScript, Java, or non-.NET | Contract-based REST API |
| Need Export() for bulk data extraction from a specific screen | Need BI/reporting data access | OData (GI-based or DAC-based) |