Acumatica Generic Inquiries API: Exposing GIs as REST and OData Endpoints
How do you expose Acumatica Generic Inquiries as API endpoints?
TL;DR
- Bottom line: Acumatica offers two ways to expose Generic Inquiries externally: (1) OData feeds via a simple checkbox -- best for read-only data extraction and BI tools; (2) Contract-based REST API via endpoint extension -- best for structured integrations needing typed entities. OData is simpler and recommended for most GI use cases.
- Key limit: REST GI access requires PUT (not GET) with
$expand=Result-- GET returns a blank <NEW> record. OData GI feeds are limited to OData v3 with restricted filter operators. - Watch out for: Pagination (
$skip/$top) does not work on nested Result collections in the REST approach -- use OData if you need server-side pagination. - Best for: Extracting custom cross-table query results from Acumatica without building custom API endpoints -- GIs let power users define the query, and the API exposes it.
- Authentication: OAuth 2.0 (recommended), cookie-based session auth, or Basic Auth for OData feeds. Bearer tokens work for both REST and OData.
System Profile
This card covers exposing Acumatica Generic Inquiries (GIs) as API endpoints. Generic Inquiries are Acumatica's power-user query builder -- they combine data from multiple DACs (Data Access Classes) into custom result sets, similar to SQL views. This card covers two exposure methods: (1) the contract-based REST API approach where you extend a Web Service Endpoint to include GI entities, and (2) the OData v3 approach where you check a box on the GI form to create an OData feed. Both methods work across all Acumatica editions. [src1, src2]
| Property | Value |
|---|---|
| Vendor | Acumatica |
| System | Acumatica ERP 2024 R2 |
| API Surface | REST (Contract-Based) + OData v3 (GI-specific) |
| Current API Version | 24.200.001 |
| Editions Covered | Small Business, Standard, Enterprise |
| Deployment | Cloud (SaaS) and on-premise |
| API Docs | Acumatica Developer Portal |
| Status | GA |
API Surfaces & Capabilities
Acumatica provides multiple API surfaces for data access. For Generic Inquiries specifically, two surfaces apply. [src1, src2]
| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| REST Contract-Based (GI via Endpoint Extension) | HTTPS/JSON | Typed entity integrations needing GI data | Limited by endpoint timeout | 50-150 req/min | Yes | No |
| OData v3 (GI Feed) | HTTPS/JSON or XML | BI tools, simple data pulls | Configurable via $top | Shared with REST | Yes | Yes (via pagination) |
| OData v4 (DAC-Based) | HTTPS/JSON | Direct table access without GI | Configurable via $top | Shared with REST | Yes | Yes |
| REST Contract-Based (Standard Entities) | HTTPS/JSON | CRUD on standard Acumatica entities | 1,000 per $batch | 50-150 req/min | Yes | Via $batch |
| SOAP (Legacy) | HTTPS/XML | Legacy integrations | Similar to REST | Shared | Yes | No |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max concurrent API sessions | 3-6 | All API calls | License-tier dependent; L Series Tier 1 = 6 concurrent |
| Session timeout (cookie-based) | ~20 minutes | Cookie auth sessions | Re-authenticate after inactivity |
| Max batch subrequests | 1,000 | REST $batch endpoint | Each subrequest counts separately |
| Default request timeout | ~10 minutes | All API calls | Long GI queries may hit this |
| Max OData $top | No hard limit | OData feeds | Large result sets impact performance |
Rolling / Daily Limits
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API requests per minute | 50-150 | Per minute | Base: ~50; L Series Tier 1: ~150; varies by license |
| Concurrent Web Service API requests | 3-6 | Per instance | Queued when exceeded; returns 429 if queue full |
| API users | License-dependent | Per instance | Monitored via SM604000 License Monitoring Console |
[src5]
Authentication
All Acumatica API access (REST and OData) supports multiple authentication methods. OAuth 2.0 is recommended for production. [src2, src7]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 (Authorization Code) | Production integrations, third-party apps | Configurable | Yes | Configure via SM303010. Recommended. |
| Cookie-Based Session | Quick testing, internal tools | ~20 min inactivity timeout | No -- re-login | POST to /entity/auth/login |
| Basic Auth (OData only) | Simple OData feeds, BI tools | Per-request | N/A | Username:password in Authorization header |
| Bearer Token (OAuth) | REST and OData after OAuth flow | Varies by config | Yes | Authorization: Bearer {token} |
Authentication Gotchas
- Cookie auth sessions count against concurrent session limits: Each unauthenticated login creates a new session. If your integration opens multiple sessions without reusing cookies, you will hit the concurrent session cap and get "API Login Limit" errors. Always reuse the session cookie or use OAuth tokens. [src5, src7]
- Basic Auth does not work for the REST contract-based API: Basic Authentication is supported for OData feeds but not for the contract-based REST endpoints. Use cookie-based login or OAuth 2.0 for REST. [src7]
- OAuth scope must include the correct instance URL: The connected application (SM303010) must have the correct callback URL and the token request must target the correct Acumatica instance URL. [src2]
Constraints
- REST GI queries require PUT, not GET -- Sending a GET request to a GI entity returns a blank <NEW> default record. You must use PUT with an empty body
{}and$expand=Resultto execute the GI and retrieve results. [src1, src6] - OData GI feeds are OData v3 only -- Even on Acumatica 2024 R2, Generic Inquiry OData feeds use OData v3 with limited operator support. OData v4 is only available for DAC-based access. [src2]
- Pagination does not work on nested REST Result collections --
$skipand$topURL parameters cannot be applied to the nested Result array returned by the REST contract-based GI approach. [src3, src6] - GI OData feeds must be explicitly enabled -- Each Generic Inquiry must have the "Expose via OData" checkbox checked. This is not enabled by default. [src2]
- DateTime filtering in REST GI endpoints requires request body -- Unlike standard OData, filtering by DateTime values on GI endpoints exposed via REST must be done by sending the filter values in the PUT request body, not as
$filterURL parameters. [src4] - API rate limits are license-dependent and shared -- REST, OData, and SOAP all share the same per-minute and concurrent session limits. Heavy GI queries count the same as simple entity reads. [src5]
Integration Pattern Decision Tree
START -- User needs to expose Acumatica Generic Inquiry data externally
|
|-- What's the primary use case?
| |-- BI / Reporting tool (Power BI, Excel, Tableau)
| | --> OData GI feed (checkbox on GI form)
| | --> URL: {instance}/OData/{tenant}/{GI_Name}?$format=json
| |
| |-- Structured integration (iPaaS, custom app, typed entities)
| | |-- Need pagination ($skip/$top)?
| | | |-- YES --> OData GI feed (REST doesn't support pagination on Results)
| | | |-- NO --> Contract-Based REST with endpoint extension
| | |
| | --> REST: PUT /entity/{endpoint}/{version}/{GI_Entity}?$expand=Result
| |
| |-- Simple one-off data pull
| | --> OData GI feed with $filter (easiest path)
| |
| |-- Writing data back based on GI results
| --> OData/REST GI for read, standard REST API for write (separate calls)
|
|-- Data volume?
| |-- < 1,000 rows --> Either approach works
| |-- 1,000-10,000 rows --> OData with $top/$skip pagination
| |-- > 10,000 rows --> OData with pagination + batch scheduling
|
|-- Authentication preference?
|-- OAuth 2.0 (recommended) --> Works for both REST and OData
|-- Basic Auth --> OData only
|-- Cookie session --> REST only
Quick Reference
| Operation | Method | Endpoint | Payload | Notes |
|---|---|---|---|---|
| Enable GI OData feed | UI | SM208000 > check "Expose via OData" | N/A | Immediate -- no deploy needed |
| Query GI via OData | GET | {instance}/OData/{tenant}/{GI_Name} | N/A | Add ?$format=json for JSON |
| Query GI via OData (24R2+) | GET | {instance}/t/{tenant}/api/odata/gi/{GI_Name} | N/A | New URL format in 2024 R2 |
| Extend endpoint for GI | UI | SM207000 > Extend Endpoint | N/A | Add entity + Results sub-entity + fields |
| Execute GI via REST | PUT | {instance}/entity/{endpoint}/{version}/{GI_Entity}?$expand=Result | {} | Empty body; PUT required, GET returns blank |
| Filter OData GI | GET | ...?$filter={field} eq '{value}' | N/A | OData v3 operators: eq, gt, lt, ge, le, ne |
| Filter REST GI | PUT | Same as Execute | {"FilterField": {"value": "..."}} | Filters in request body, not URL |
| Paginate OData GI | GET | ...?$top=100&$skip=200 | N/A | Works on OData; NOT on REST Results |
| Login (cookie auth) | POST | /entity/auth/login | {"name":"...","password":"...","company":"..."} | Returns session cookie |
| Logout | POST | /entity/auth/logout | N/A | Always logout to free session slot |
Step-by-Step Integration Guide
1. Create or identify the Generic Inquiry
Navigate to SM208000 (Generic Inquiry form) and either create a new GI or identify an existing one. The GI defines the data sources (DACs), joins, conditions, and result columns. [src1]
SM208000 -- Generic Inquiry configuration:
1. Design ID: e.g., "InventoryByLocation"
2. Add tables under "Tables" tab
3. Define joins between tables
4. Add result columns under "Results" tab
5. (Optional) Add parameters for runtime filtering
6. Save the Generic Inquiry
Verify: Open the GI from the navigation menu and confirm it returns the expected data.
2a. Expose via OData (recommended for most use cases)
On the SM208000 form, check the "Expose via OData" checkbox and save. The OData feed is immediately available. [src2]
# OData v3 URL (all versions):
curl -s -u "admin:password" \
"https://yourinstance.acumatica.com/OData/YourTenant/InventoryByLocation?\$format=json&\$top=10"
# OData URL (2024 R2+ new format):
curl -s -u "admin:password" \
"https://yourinstance.acumatica.com/t/YourTenant/api/odata/gi/InventoryByLocation?\$format=json&\$top=10"
Verify: Response contains a value array with GI result rows.
2b. Expose via REST Contract-Based API
Navigate to SM207000 (Web Service Endpoints) and extend the Default endpoint. Add a new top-level entity mapped to the GI, then create a Results sub-entity. [src1]
SM207000 -- Web Service Endpoint Extension:
1. Select the "Default" endpoint for your version (e.g., 24.200.001)
2. Click "Extend Endpoint" -- name it (e.g., "MyExtEndpoint")
3. Insert new top-level entity: Name = "InventoryByLocation"
4. Under entity, add sub-entity: Name = "Result", ObjectName = "InvByLocation"
5. Under "Result", add fields: ItemID, WarehouseID, LocationID, QtyOnHand
6. Save the endpoint
Verify: Entity appears in endpoint Swagger at {instance}/entity/{endpoint}/{version}/$adoc.
3. Query the GI via REST (PUT method)
Execute the GI by sending a PUT request with an empty body and $expand=Result. GET will NOT work. [src1, src6]
# Login
curl -s -X POST "https://yourinstance.acumatica.com/entity/auth/login" \
-H "Content-Type: application/json" \
-d '{"name":"admin","password":"yourpassword","company":"YourCompany"}' \
-c cookies.txt
# Execute GI via PUT
curl -s -X PUT \
"https://yourinstance.acumatica.com/entity/MyExtEndpoint/24.200.001/InventoryByLocation?\$expand=Result" \
-H "Content-Type: application/json" \
-b cookies.txt -d '{}'
# Logout
curl -s -X POST "https://yourinstance.acumatica.com/entity/auth/logout" -b cookies.txt
Verify: Response JSON contains "Result": [...] with data rows.
4. Apply filters to narrow results
For OData, use $filter URL parameters. For REST, send filter values in the PUT request body. [src4]
# OData filtering -- URL parameters:
curl -s -u "admin:password" \
"https://yourinstance.acumatica.com/OData/YourTenant/InventoryByLocation?\$filter=WarehouseID eq 'WHOLESALE'&\$top=50&\$format=json"
# REST filtering -- values in request body:
curl -s -X PUT \
"https://yourinstance.acumatica.com/entity/MyExtEndpoint/24.200.001/InventoryByLocation?\$expand=Result" \
-H "Content-Type: application/json" -b cookies.txt \
-d '{"WarehouseID": {"value": "WHOLESALE"}}'
Verify: Returned results are filtered to matching records only.
Code Examples
Python: Query GI via OData with Bearer Token
# Input: Acumatica instance URL, OAuth access token, GI name
# Output: JSON result rows from the Generic Inquiry
import requests
instance_url = "https://yourinstance.acumatica.com"
access_token = "YOUR_OAUTH_ACCESS_TOKEN"
gi_name = "InventoryByLocation"
tenant = "YourTenant"
odata_url = f"{instance_url}/t/{tenant}/api/odata/gi/{gi_name}"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
params = {
"$format": "json",
"$top": 100,
"$filter": "WarehouseID eq 'WHOLESALE'"
}
response = requests.get(odata_url, headers=headers, params=params)
response.raise_for_status()
data = response.json()
for row in data.get("value", []):
print(f"Item: {row['ItemID']}, Warehouse: {row['WarehouseID']}")
JavaScript/Node.js: Query GI via REST Contract-Based API
// Input: Acumatica instance URL, credentials, endpoint name
// Output: GI result rows from the contract-based REST endpoint
const fetch = require("node-fetch"); // npm install node-fetch@2
const instanceUrl = "https://yourinstance.acumatica.com";
const endpoint = "MyExtEndpoint";
const version = "24.200.001";
async function queryGI() {
// Login
const loginResp = await fetch(`${instanceUrl}/entity/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "admin", password: "pass", company: "Co" }),
redirect: "manual"
});
const cookies = loginResp.headers.raw()["set-cookie"];
const cookieHeader = cookies.map(c => c.split(";")[0]).join("; ");
// Execute GI via PUT (NOT GET)
const giResp = await fetch(
`${instanceUrl}/entity/${endpoint}/${version}/InventoryByLocation?$expand=Result`,
{ method: "PUT", headers: { "Content-Type": "application/json", Cookie: cookieHeader }, body: "{}" }
);
const data = await giResp.json();
data.Result?.forEach(row =>
console.log(`Item: ${row.ItemID?.value}, Qty: ${row.QtyOnHand?.value}`)
);
// Logout
await fetch(`${instanceUrl}/entity/auth/logout`, { method: "POST", headers: { Cookie: cookieHeader } });
}
queryGI().catch(console.error);
cURL: Quick OData GI test with Basic Auth
# Input: Acumatica instance URL, credentials, GI name
# Output: JSON result set from Generic Inquiry
curl -s -u "admin:password" \
"https://yourinstance.acumatica.com/OData/YourTenant/InventoryByLocation?\$format=json&\$top=5" \
-H "Accept: application/json" | jq .
# Expected output:
# {
# "value": [
# {"ItemID": "ITEM001", "WarehouseID": "WHOLESALE", "QtyOnHand": 150},
# ...
# ]
# }
Data Mapping
Field Mapping Reference
| GI Result Column | REST Response Field | OData Response Field | Type | Gotcha |
|---|---|---|---|---|
| Inventory ID | ItemID.value | ItemID | String | REST wraps in {value: "..."}; OData returns plain value |
| Warehouse | WarehouseID.value | WarehouseID | String | API name matches GI column alias, not DAC field name |
| Quantity On Hand | QtyOnHand.value | QtyOnHand | Decimal | REST returns as string; OData returns as number |
| Last Modified Date | LastModified.value | LastModified | DateTime | REST: ISO 8601; OData v3: /Date(timestamp)/ |
| Boolean/Checkbox | IsActive.value | IsActive | Boolean | OData filter: use lowercase true/false or 0/1 |
| Custom field | UsrCustomField.value | UsrCustomField | Varies | Custom fields require USR prefix |
Data Type Gotchas
- REST wraps every field in a
{value: "..."}object: Unlike OData which returns plain values, the Acumatica contract-based REST API wraps every field. Your deserialization must unwrap this layer. [src1, src7] - OData v3 DateTime uses legacy
/Date()/notation: OData v3 GI feeds return dates as/Date(1234567890000)/(milliseconds since epoch), not ISO 8601. [src2] - Decimal precision varies by field type: Currency fields return 4 decimal places; quantity fields return 6. REST returns these as strings. [src1]
- Boolean fields in OData filters must be lowercase: Use
$filter=IsActive eq true(not TRUE). Alternatively use 1 or 0. [src4]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 | Unauthorized | Invalid credentials, expired token, or session timeout | Re-authenticate; check OAuth token expiry |
| 403 | Forbidden | User lacks access to GI or underlying DACs | Grant GI access rights and entity-level permissions |
| 404 | Not Found | Wrong endpoint name, version, or GI entity name | Verify endpoint includes GI entity; check URL and version |
| 429 | Too Many Requests | Exceeded per-minute API limit or concurrent session cap | Implement backoff; check SM604000 for limits |
| 500 | Internal Server Error | BQL query error, timeout, or missing DAC permissions | Check GI runs manually in UI; review trace logs |
| <NEW> record | Used GET instead of PUT | GET on GI entity returns default/blank record by design | Switch to PUT with empty body and $expand=Result |
| Empty Result | No data or misconfigured | Results sub-entity not mapped or GI conditions exclude all records | Verify GI returns data in UI; check endpoint mapping |
Failure Points in Production
- Session exhaustion from unauthenticated login loops: Integration creates a new session per call without logging out, exhausting the 3-6 concurrent limit within seconds. Fix:
Reuse session cookies and call /entity/auth/logout when done. Use OAuth 2.0 bearer tokens.[src5, src7] - GI timeout on large result sets: GIs joining many tables or returning >10,000 rows timeout at the ~10 min server limit. Fix:
Add GI parameters to pre-filter server-side. Use OData $top/$skip pagination.[src1] - Endpoint version mismatch after upgrade: Old endpoint version in URL stops working after Acumatica upgrade. Fix:
Store endpoint version in config. Re-extend endpoint for new version after upgrade.[src1, src7] - OData feed returns stale data after GI modification: Someone modifies the GI columns/joins, and the OData feed returns errors or missing fields. Fix:
Version-control GI definitions via customization projects. Test OData output after any GI modification.[src2] - Boolean filter values rejected: Using
$filter=IsActive eq TRUE(uppercase) on OData v3 fails. Fix:Use lowercase: $filter=IsActive eq true, or numeric: $filter=IsActive eq 1.[src4]
Anti-Patterns
Wrong: Using GET to retrieve GI data via REST
# BAD -- GET returns a blank <NEW> default record, not the GI results
curl -s -X GET \
"https://instance.acumatica.com/entity/MyEndpoint/24.200.001/InventoryByLocation?$expand=Result" \
-b cookies.txt
# Returns: {"id": "...", "Result": [], ...} -- empty results
Correct: Use PUT with empty body to execute the GI
# GOOD -- PUT triggers the GI execution and returns results
curl -s -X PUT \
"https://instance.acumatica.com/entity/MyEndpoint/24.200.001/InventoryByLocation?$expand=Result" \
-H "Content-Type: application/json" -b cookies.txt -d '{}'
# Returns: {"id": "...", "Result": [{"ItemID": {"value": "ITEM001"}, ...}]}
Wrong: Opening a new session for every API call
# BAD -- creates new session per request, exhausts concurrent session limit
for item_id in item_ids:
session = requests.Session()
session.post(f"{url}/entity/auth/login", json=creds) # new session each time
result = session.put(f"{url}/entity/endpoint/ver/GI?$expand=Result",
json={"ItemID": {"value": item_id}})
# Never logs out -- session slot consumed until timeout
Correct: Reuse session and logout when done
# GOOD -- single session, reused, properly closed
session = requests.Session()
session.post(f"{url}/entity/auth/login", json=creds)
for item_id in item_ids:
result = session.put(f"{url}/entity/endpoint/ver/GI?$expand=Result",
json={"ItemID": {"value": item_id}})
process(result.json())
session.post(f"{url}/entity/auth/logout") # free the session slot
Wrong: Trying to paginate REST GI results with $skip
# BAD -- $skip/$top do not work on nested Result collections
curl -s -X PUT \
"https://instance.acumatica.com/entity/endpoint/ver/GI?$expand=Result&$skip=100&$top=50" \
-H "Content-Type: application/json" -b cookies.txt -d '{}'
# $skip is ignored on nested Results -- returns all rows
Correct: Use OData for paginated GI access
# GOOD -- OData supports $skip/$top natively on GI feeds
curl -s -u "admin:password" \
"https://instance.acumatica.com/OData/Tenant/InventoryByLocation?\$top=50&\$skip=100&\$format=json"
# Returns rows 101-150 as expected
Common Pitfalls
- Forgetting
$expand=Result: Without this parameter, the PUT returns only top-level entity fields, not GI data rows. Fix:Always append ?$expand=Result to the REST GI URL.[src1] - Mapping fields at top entity instead of Results sub-entity: Adding GI fields directly under the top-level entity instead of a Results sub-entity causes empty results. Fix:
Create hierarchy: TopEntity > Result (sub-entity) > Fields.[src1] - Using wrong ObjectName for Results sub-entity: The ObjectName must be unique and different from the top-level entity. Fix:
Use a distinct name like "InvByLocation" for the Results ObjectName.[src1] - Not checking "Expose via OData" checkbox: Creating a GI does not auto-enable OData access. Fix:
Open SM208000, check the box, save. No deployment needed.[src2] - DateTime filter syntax errors in OData v3: OData v3 requires specific formatting. Fix:
Use: $filter=LastModified gt datetime'2024-01-01T00:00:00'.[src4] - Ignoring "API Login Limit" errors: This is session exhaustion, not rate limiting. Fix:
Reduce concurrent sessions, implement proper logout, or use OAuth 2.0.[src5]
Diagnostic Commands
# Check if GI OData feed is accessible (Basic Auth)
curl -s -o /dev/null -w "%{http_code}" -u "admin:password" \
"https://yourinstance.acumatica.com/OData/YourTenant/YourGIName?\$format=json&\$top=1"
# Expected: 200
# List available OData GI feeds (metadata)
curl -s -u "admin:password" \
"https://yourinstance.acumatica.com/OData/YourTenant/\$metadata" \
-H "Accept: application/xml"
# Test REST authentication (cookie-based)
curl -s -X POST "https://yourinstance.acumatica.com/entity/auth/login" \
-H "Content-Type: application/json" \
-d '{"name":"admin","password":"password","company":"Company"}' \
-c cookies.txt -o /dev/null -w "%{http_code}"
# Expected: 204
# Execute GI and count result rows
curl -s -X PUT \
"https://yourinstance.acumatica.com/entity/MyEndpoint/24.200.001/InventoryByLocation?\$expand=Result" \
-H "Content-Type: application/json" -b cookies.txt -d '{}' \
| jq '.Result | length'
# Check API license limits: navigate to SM604000 in Acumatica UI
Version History & Compatibility
| API Version | Release Date | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| 24.200.001 (2024 R2) | 2024-10 | Current | New OData GI URL format | Old URL still works; new format recommended |
| 23.200.001 (2023 R2) | 2023-10 | Supported | OData GI parameter filtering added | GIs with parameters can now be filtered via OData |
| 23.100.001 (2023 R1) | 2023-04 | Supported | OAuth 2.0 connected applications | New auth flow; cookie-based still supported |
| 22.200.001 (2022 R2) | 2022-10 | Supported | None for GI APIs | -- |
| 18.200.001 (2019 R1) | 2019-01 | EOL | Contract-Based API GI support introduced | Original version for GI endpoint extension |
Deprecation Policy
Acumatica supports API endpoints for the current and previous two major releases (approximately 3 years). Older endpoint versions may stop working after an upgrade. Extended endpoints must be re-created or migrated when upgrading to a new major version. [src1, src7]
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Need custom cross-table query results already defined as a GI | Need CRUD operations (create/update/delete) | Standard Acumatica REST contract-based API |
| Connecting BI tools (Power BI, Excel) to Acumatica data | Need real-time webhooks or push notifications | Acumatica Push Notifications (SM302000) or Webhooks (SM304000) |
| Want power users to define queries without developer involvement | Query needs OData v4 features ($apply, $compute) | OData v4 DAC-based access |
| Result set is under 10,000 rows | Need to extract millions of records for data warehouse | Direct database replication or data export |
| Need pagination on large result sets | Need pagination via REST (only works on OData) | OData GI feed with $top/$skip |
Important Caveats
- The REST contract-based approach for GIs (PUT with $expand=Result) mimics "opening the screen and clicking refresh." Performance depends on the GI's underlying BQL complexity, not just API call overhead. [src1, src6]
- OData v3 GI feeds have limited operator support compared to full OData v4 -- complex expressions like
$apply,$compute, andsubstringof()may not work. Test before relying on them in production. [src2] - Rate limits in SM604000 are license entitlements, not hard server limits -- exceeding them queues requests rather than immediately rejecting. But if the queue fills, subsequent requests are declined with 429 errors. [src5]
- GI OData feeds may bypass Acumatica's standard API logging. Monitor via IIS logs or application-level logging. [src2]
- Acumatica version upgrades can break extended endpoints -- always test GI API access in a sandbox after upgrading. [src1, src7]