Acumatica Generic Inquiries API: Exposing GIs as REST and OData Endpoints

Type: ERP Integration System: Acumatica ERP (24.200.001) Confidence: 0.88 Sources: 7 Verified: 2026-03-02 Freshness: 2026-03-02

TL;DR

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]

PropertyValue
VendorAcumatica
SystemAcumatica ERP 2024 R2
API SurfaceREST (Contract-Based) + OData v3 (GI-specific)
Current API Version24.200.001
Editions CoveredSmall Business, Standard, Enterprise
DeploymentCloud (SaaS) and on-premise
API DocsAcumatica Developer Portal
StatusGA

API Surfaces & Capabilities

Acumatica provides multiple API surfaces for data access. For Generic Inquiries specifically, two surfaces apply. [src1, src2]

API SurfaceProtocolBest ForMax Records/RequestRate LimitReal-time?Bulk?
REST Contract-Based (GI via Endpoint Extension)HTTPS/JSONTyped entity integrations needing GI dataLimited by endpoint timeout50-150 req/minYesNo
OData v3 (GI Feed)HTTPS/JSON or XMLBI tools, simple data pullsConfigurable via $topShared with RESTYesYes (via pagination)
OData v4 (DAC-Based)HTTPS/JSONDirect table access without GIConfigurable via $topShared with RESTYesYes
REST Contract-Based (Standard Entities)HTTPS/JSONCRUD on standard Acumatica entities1,000 per $batch50-150 req/minYesVia $batch
SOAP (Legacy)HTTPS/XMLLegacy integrationsSimilar to RESTSharedYesNo

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Max concurrent API sessions3-6All API callsLicense-tier dependent; L Series Tier 1 = 6 concurrent
Session timeout (cookie-based)~20 minutesCookie auth sessionsRe-authenticate after inactivity
Max batch subrequests1,000REST $batch endpointEach subrequest counts separately
Default request timeout~10 minutesAll API callsLong GI queries may hit this
Max OData $topNo hard limitOData feedsLarge result sets impact performance

[src5, src7]

Rolling / Daily Limits

Limit TypeValueWindowEdition Differences
API requests per minute50-150Per minuteBase: ~50; L Series Tier 1: ~150; varies by license
Concurrent Web Service API requests3-6Per instanceQueued when exceeded; returns 429 if queue full
API usersLicense-dependentPer instanceMonitored 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]

FlowUse WhenToken LifetimeRefresh?Notes
OAuth 2.0 (Authorization Code)Production integrations, third-party appsConfigurableYesConfigure via SM303010. Recommended.
Cookie-Based SessionQuick testing, internal tools~20 min inactivity timeoutNo -- re-loginPOST to /entity/auth/login
Basic Auth (OData only)Simple OData feeds, BI toolsPer-requestN/AUsername:password in Authorization header
Bearer Token (OAuth)REST and OData after OAuth flowVaries by configYesAuthorization: Bearer {token}

Authentication Gotchas

Constraints

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

OperationMethodEndpointPayloadNotes
Enable GI OData feedUISM208000 > check "Expose via OData"N/AImmediate -- no deploy needed
Query GI via ODataGET{instance}/OData/{tenant}/{GI_Name}N/AAdd ?$format=json for JSON
Query GI via OData (24R2+)GET{instance}/t/{tenant}/api/odata/gi/{GI_Name}N/ANew URL format in 2024 R2
Extend endpoint for GIUISM207000 > Extend EndpointN/AAdd entity + Results sub-entity + fields
Execute GI via RESTPUT{instance}/entity/{endpoint}/{version}/{GI_Entity}?$expand=Result{}Empty body; PUT required, GET returns blank
Filter OData GIGET...?$filter={field} eq '{value}'N/AOData v3 operators: eq, gt, lt, ge, le, ne
Filter REST GIPUTSame as Execute{"FilterField": {"value": "..."}}Filters in request body, not URL
Paginate OData GIGET...?$top=100&$skip=200N/AWorks on OData; NOT on REST Results
Login (cookie auth)POST/entity/auth/login{"name":"...","password":"...","company":"..."}Returns session cookie
LogoutPOST/entity/auth/logoutN/AAlways 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 ColumnREST Response FieldOData Response FieldTypeGotcha
Inventory IDItemID.valueItemIDStringREST wraps in {value: "..."}; OData returns plain value
WarehouseWarehouseID.valueWarehouseIDStringAPI name matches GI column alias, not DAC field name
Quantity On HandQtyOnHand.valueQtyOnHandDecimalREST returns as string; OData returns as number
Last Modified DateLastModified.valueLastModifiedDateTimeREST: ISO 8601; OData v3: /Date(timestamp)/
Boolean/CheckboxIsActive.valueIsActiveBooleanOData filter: use lowercase true/false or 0/1
Custom fieldUsrCustomField.valueUsrCustomFieldVariesCustom fields require USR prefix

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
401UnauthorizedInvalid credentials, expired token, or session timeoutRe-authenticate; check OAuth token expiry
403ForbiddenUser lacks access to GI or underlying DACsGrant GI access rights and entity-level permissions
404Not FoundWrong endpoint name, version, or GI entity nameVerify endpoint includes GI entity; check URL and version
429Too Many RequestsExceeded per-minute API limit or concurrent session capImplement backoff; check SM604000 for limits
500Internal Server ErrorBQL query error, timeout, or missing DAC permissionsCheck GI runs manually in UI; review trace logs
<NEW> recordUsed GET instead of PUTGET on GI entity returns default/blank record by designSwitch to PUT with empty body and $expand=Result
Empty ResultNo data or misconfiguredResults sub-entity not mapped or GI conditions exclude all recordsVerify GI returns data in UI; check endpoint mapping

[src1, src5, src7]

Failure Points in Production

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

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 VersionRelease DateStatusBreaking ChangesMigration Notes
24.200.001 (2024 R2)2024-10CurrentNew OData GI URL formatOld URL still works; new format recommended
23.200.001 (2023 R2)2023-10SupportedOData GI parameter filtering addedGIs with parameters can now be filtered via OData
23.100.001 (2023 R1)2023-04SupportedOAuth 2.0 connected applicationsNew auth flow; cookie-based still supported
22.200.001 (2022 R2)2022-10SupportedNone for GI APIs--
18.200.001 (2019 R1)2019-01EOLContract-Based API GI support introducedOriginal version for GI endpoint extension

[src1, src2]

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 WhenDon't Use WhenUse Instead
Need custom cross-table query results already defined as a GINeed CRUD operations (create/update/delete)Standard Acumatica REST contract-based API
Connecting BI tools (Power BI, Excel) to Acumatica dataNeed real-time webhooks or push notificationsAcumatica Push Notifications (SM302000) or Webhooks (SM304000)
Want power users to define queries without developer involvementQuery needs OData v4 features ($apply, $compute)OData v4 DAC-based access
Result set is under 10,000 rowsNeed to extract millions of records for data warehouseDirect database replication or data export
Need pagination on large result setsNeed pagination via REST (only works on OData)OData GI feed with $top/$skip

Important Caveats

Related Units