---
# === IDENTITY ===
id: business/erp-integration/acumatica-generic-inquiries-api/2026
canonical_question: "How do you expose Acumatica Generic Inquiries as API endpoints?"
aliases:
  - "How to access Acumatica Generic Inquiry data via REST API"
  - "Acumatica GI OData endpoint configuration and filtering"
  - "Exposing custom Acumatica Generic Inquiries to external systems via API"
  - "Acumatica contract-based API for Generic Inquiries vs OData"
entity_type: erp_integration
domain: business > erp-integration > acumatica-generic-inquiries-api
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Acumatica ERP"
    vendor: "Acumatica"
    version: "2024 R2 (24.200.001)"
    edition: "All editions (Small Business, Standard, Enterprise)"
    deployment: "cloud"
    api_surface: "REST (Contract-Based) + OData v3"

# === 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: "2023 R2 — OData GI parameter filtering added; 2024 R2 — new OData GI URL format"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "REST API for GIs requires PUT method with empty body and $expand=Result — GET returns a <NEW> default record, not query results"
  - "OData GI feeds use OData v3 only (not v4) with limited operator support compared to full OData spec"
  - "$skip and $top pagination cannot be applied to nested Result collections in the REST contract-based approach — only works at OData top level"
  - "API rate limits are license-tier dependent: base ~50 req/min, L Series Tier 1 ~150 req/min; concurrent sessions typically 3-6"
  - "GI OData feeds must be explicitly enabled per inquiry via the 'Expose via OData' checkbox — not exposed by default"
  - "Filtering on DateTime fields in REST GI endpoints requires values in the request body, not URL $filter parameters"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs direct DAC-level data access without a Generic Inquiry"
    use_instead: "business/erp-integration/acumatica-rest-api/2026"
  - condition: "User needs to write/create records in Acumatica, not just read GI results"
    use_instead: "business/erp-integration/acumatica-rest-api/2026"
  - condition: "User needs OData v4 DAC-based access (not GI-based)"
    use_instead: "business/erp-integration/acumatica-odata-v4/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "real-time read (query GI results on demand)"
      - "batch/scheduled data extraction (periodic pull)"
      - "reporting/BI tool integration (Power BI, Excel)"
      - "middleware integration (iPaaS, ETL tool)"
  - key: data_volume
    question: "What's your expected result set size?"
    type: choice
    options:
      - "< 1,000 rows per query"
      - "1,000-10,000 rows per query"
      - "> 10,000 rows per query"
  - key: direction
    question: "What's the data flow direction?"
    type: choice
    options:
      - "outbound (reading GI data from Acumatica)"
      - "bidirectional (read GI + write back via REST)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/acumatica-generic-inquiries-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-rest-api/2026"
      label: "Acumatica contract-based REST API capabilities and rate limits"
  solves: []
  alternative_to:
    - id: "business/erp-integration/acumatica-odata-v4/2026"
      label: "Acumatica OData v4 DAC-based data access (no GI required)"
  often_confused_with:
    - id: "business/erp-integration/acumatica-rest-api/2026"
      label: "Acumatica standard REST API for entity CRUD (different from GI exposure)"

# === SOURCES ===
sources:
  - id: src1
    title: "Accessing Data in Generic Inquiries using Contract Based API's"
    author: Acumatica
    url: https://www.acumatica.com/blog/contract-based-apis-in-generic-inquiries/
    type: official_docs
    published: 2025-07-15
    reliability: authoritative
  - id: src2
    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: 2025-01-20
    reliability: authoritative
  - id: src3
    title: "Using GI's with REST API — Acumatica Community"
    author: Acumatica Community
    url: https://community.acumatica.com/develop-integrations-with-web-services-apis-289/using-gi-s-with-rest-api-6759
    type: community_resource
    published: 2024-06-10
    reliability: moderate_high
  - id: src4
    title: "Exposing Generic Inquiry to REST API — Acumatica Community"
    author: Acumatica Community
    url: https://community.acumatica.com/develop-integrations-with-web-services-apis-289/exposing-generic-inquiry-to-rest-api-23190
    type: community_resource
    published: 2024-09-15
    reliability: moderate_high
  - id: src5
    title: "License Restrictions for API Users — Acumatica Help"
    author: Acumatica
    url: https://help.acumatica.com/(W(1))/Wiki/ShowWiki.aspx?pageid=7a796856-3dec-4a4f-abf8-171324c9642b
    type: official_docs
    published: 2024-10-01
    reliability: authoritative
  - id: src6
    title: "Generic Inquiry via REST API — AUG Forums"
    author: AUG Forums
    url: https://www.augforums.com/forums/acumatica-generic-inquiries/generic-inquiry-via-rest-api/
    type: community_resource
    published: 2024-08-20
    reliability: moderate_high
  - id: src7
    title: "Acumatica REST API Documentation: Examples & Best Practices"
    author: Concentrus
    url: https://concentrus.com/acumatica-rest-api-documentation/
    type: technical_blog
    published: 2025-03-10
    reliability: moderate_high
---

# Acumatica Generic Inquiries API: Exposing GIs as REST and OData 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. [src1, src2]
- **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. [src1, src3]
- **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. [src3, src6]
- **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. [src2, src7]

## 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. The OData v4 DAC-based approach (which bypasses GIs entirely) is a separate topic. [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 (2024 R2) |
| **Editions Covered** | Small Business, Standard, Enterprise (all support GI API exposure) |
| **Deployment** | Cloud (SaaS) and on-premise |
| **API Docs URL** | [Acumatica Developer Portal](https://www.acumatica.com/developers/) |
| **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 (~10 min default) | 50-150 req/min (license-dependent) | Yes | No |
| OData v3 (GI Feed) | HTTPS/JSON or XML | BI tools (Power BI, Excel), simple data pulls | Configurable via $top, server default varies | Shared with REST limits | Yes | Yes (via pagination) |
| OData v4 (DAC-Based) | HTTPS/JSON | Direct table access without GI | Configurable via $top | Shared with REST limits | 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 |

[src1, src2, src5]

## 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 | Server-side; long GI queries may hit this |
| Max OData $top | No hard limit | OData feeds | But large result sets impact performance |

[src5, src7]

### 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 (Connected Applications). Recommended. [src2] |
| Cookie-Based Session | Quick testing, internal tools | ~20 min inactivity timeout | No — re-login required | POST to /entity/auth/login with JSON credentials [src7] |
| Basic Auth (OData only) | Simple OData feeds, BI tools | Per-request | N/A | Username:password in Authorization header. Works for OData GI feeds. [src3] |
| Bearer Token (OAuth) | REST and OData after OAuth flow | Varies by config | Yes | Include as Authorization: Bearer {token} header [src2] |

### 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, which is by design (mimics opening the screen manually). You must use PUT with an empty body `{}` and `$expand=Result` to 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 (direct table) access, not GI-based. [src2]
- **Pagination does not work on nested REST Result collections** — `$skip` and `$top` URL parameters cannot be applied to the nested Result array returned by the REST contract-based GI approach. For paginated GI access, use OData instead. [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, not URL** — 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 `$filter` URL parameters. [src4]
- **API rate limits are license-dependent and affect all API surfaces** — 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 integration (Power BI, Excel, Tableau)
|   |   --> OData GI feed (checkbox on GI form, immediate availability)
|   |   --> 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
|   |   |
|   |   |-- Need typed entity validation?
|   |   |   |-- YES --> Contract-Based REST (entity schema + field types enforced)
|   |   |   |-- NO --> OData GI feed (simpler setup)
|   |   |
|   |   --> REST: PUT /entity/{endpoint}/{version}/{GI_Entity}?$expand=Result
|   |
|   |-- Simple one-off data pull
|   |   --> OData GI feed with $filter (easiest path)
|   |
|   |-- Writing data back to Acumatica based on GI results
|       --> Use 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 + consider batch scheduling
|
|-- Authentication preference?
    |-- OAuth 2.0 (recommended) --> Works for both REST and OData
    |-- Basic Auth --> OData only
    |-- Cookie session --> REST only (not recommended for production)
```

## Quick Reference

| Operation | Method | Endpoint | Payload | Notes |
|---|---|---|---|---|
| Enable GI OData feed | UI | SM208000 (Generic Inquiry) > check "Expose via OData" | N/A | Immediate — no deploy needed [src2] |
| Query GI via OData | GET | `{instance}/OData/{tenant}/{GI_Name}` | N/A | Add `?$format=json` for JSON output [src3] |
| Query GI via OData (24R2+) | GET | `{instance}/t/{tenant}/api/odata/gi/{GI_Name}` | N/A | New URL format in 2024 R2 [src2] |
| Extend endpoint for GI | UI | SM207000 (Web Service Endpoints) > Extend Endpoint | N/A | Add entity + Results sub-entity + fields [src1] |
| Execute GI via REST | PUT | `{instance}/entity/{endpoint}/{version}/{GI_Entity}?$expand=Result` | `{}` | Empty body; PUT required, GET returns blank [src1] |
| Filter OData GI | GET | `{instance}/OData/{tenant}/{GI_Name}?$filter={field} eq '{value}'` | N/A | OData v3 operators: eq, gt, lt, ge, le, ne [src4] |
| Filter REST GI | PUT | `{instance}/entity/{endpoint}/{version}/{GI_Entity}?$expand=Result` | `{"FilterField": {"value": "..."}}` | Filters in request body, not URL [src4] |
| Paginate OData GI | GET | `{instance}/OData/{tenant}/{GI_Name}?$top=100&$skip=200` | N/A | Works on OData; NOT on REST Results [src3, src6] |
| Login (cookie auth) | POST | `{instance}/entity/auth/login` | `{"name":"...","password":"...","company":"..."}` | Returns session cookie [src7] |
| Logout | POST | `{instance}/entity/auth/logout` | N/A | Always logout to free session slot [src7] |

## 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. Ensure all required fields appear in the Results grid. [src1]

```plaintext
SM208000 — Generic Inquiry configuration:
1. Design ID: e.g., "InventoryByLocation"
2. Add tables under "Tables" tab (e.g., InventoryItem, INLocationStatus)
3. Define joins between tables
4. Add result columns under "Results" tab
5. (Optional) Add parameters under "Parameters" tab 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 at the OData URL. No deployment or restart is needed. [src2]

```bash
# Input:  Acumatica instance URL, tenant name, GI name
# Output: JSON result set from the Generic Inquiry

# OData v3 URL (all versions):
curl -s -u "admin:password" \
  "https://yourinstance.acumatica.com/OData/YourTenant/InventoryByLocation?\$format=json&\$top=10" \
  -H "Accept: application/json"

# 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" \
  -H "Accept: application/json"
```

**Verify**: The response contains a `value` array with GI result rows. Each row has fields matching the GI's Results columns.

### 2b. Expose via REST Contract-Based API (for typed entity integrations)

Navigate to SM207000 (Web Service Endpoints) and extend the Default endpoint. Add a new top-level entity mapped to the GI's Screen ID, then create a Results sub-entity with individual field mappings. [src1]

```plaintext
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", Screen ID = (GI screen)
4. Under the top entity, add a sub-entity: Name = "Result", ObjectName = "InvByLocation"
5. Under "Result", add fields: ItemID, WarehouseID, LocationID, QtyOnHand, etc.
6. Save the endpoint
```

**Verify**: The entity appears in the endpoint's Swagger documentation at `{instance}/entity/{endpoint}/{version}/$adoc`.

### 3. Query the GI via REST (PUT method)

Execute the Generic Inquiry by sending a PUT request with an empty body and the `$expand=Result` parameter. This is the most critical step — GET will NOT work. [src1, src6]

```bash
# Input:  Authenticated session (cookie or Bearer token), endpoint name, GI entity name
# Output: JSON with nested Result array containing GI data

# Step 1: Login (cookie-based)
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

# Step 2: 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 '{}'

# Step 3: Logout (free session)
curl -s -X POST "https://yourinstance.acumatica.com/entity/auth/logout" \
  -b cookies.txt
```

**Verify**: Response JSON contains `"Result": [...]` with data rows. Each row has the field names defined in step 2b.

### 4. Apply filters to narrow results

For OData, use standard `$filter` URL parameters. For REST, send filter values in the PUT request body. [src4]

```bash
# OData filtering — URL parameters (simple and reliable):
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 (required for DateTime):
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 only matching records. OData `$filter` supports: `eq`, `ne`, `gt`, `lt`, `ge`, `le`, `and`, `or`.

## Code Examples

### Python: Query GI via OData with Bearer Token

```python
# 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"  # from OAuth 2.0 flow
gi_name = "InventoryByLocation"
tenant = "YourTenant"

# OData GI URL (2024 R2+ format)
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']}, Qty: {row['QtyOnHand']}")

# Pagination: use $skip for next page
if "@odata.nextLink" in data:
    next_url = data["@odata.nextLink"]
    next_response = requests.get(next_url, headers=headers)
    # Process next page...
```

### JavaScript/Node.js: Query GI via REST Contract-Based API

```javascript
// Input:  Acumatica instance URL, credentials, endpoint name, GI entity name
// Output: GI result rows from the contract-based REST endpoint

// npm install node-fetch@2
const fetch = require("node-fetch");

const instanceUrl = "https://yourinstance.acumatica.com";
const endpoint = "MyExtEndpoint";
const version = "24.200.001";
const giEntity = "InventoryByLocation";

async function queryGI() {
  // Step 1: Login (cookie-based auth)
  const loginResp = await fetch(`${instanceUrl}/entity/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: "admin",
      password: "yourpassword",
      company: "YourCompany"
    }),
    redirect: "manual"
  });
  const cookies = loginResp.headers.raw()["set-cookie"];
  const cookieHeader = cookies.map(c => c.split(";")[0]).join("; ");

  // Step 2: Execute GI via PUT (NOT GET)
  const giResp = await fetch(
    `${instanceUrl}/entity/${endpoint}/${version}/${giEntity}?$expand=Result`,
    {
      method: "PUT",
      headers: {
        "Content-Type": "application/json",
        Cookie: cookieHeader
      },
      body: JSON.stringify({})  // empty body to execute GI
    }
  );
  const giData = await giResp.json();

  // Step 3: Process results
  if (giData.Result) {
    giData.Result.forEach(row => {
      console.log(`Item: ${row.ItemID?.value}, Qty: ${row.QtyOnHand?.value}`);
    });
  }

  // Step 4: Logout (free session slot)
  await fetch(`${instanceUrl}/entity/auth/logout`, {
    method: "POST",
    headers: { Cookie: cookieHeader }
  });
}

queryGI().catch(console.error);
```

### cURL: Quick OData GI test with Basic Auth

```bash
# Input:  Acumatica instance URL, credentials, GI name
# Output: JSON result set from Generic Inquiry

# Simple OData query with Basic Auth:
curl -s -u "admin:password" \
  "https://yourinstance.acumatica.com/OData/YourTenant/InventoryByLocation?\$format=json&\$top=5" \
  -H "Accept: application/json" | jq .

# Expected output:
# {
#   "@odata.context": "...",
#   "value": [
#     {
#       "ItemID": "ITEM001",
#       "WarehouseID": "WHOLESALE",
#       "LocationID": "LOC-01",
#       "QtyOnHand": 150
#     },
#     ...
#   ]
# }

# Filtered query:
curl -s -u "admin:password" \
  "https://yourinstance.acumatica.com/OData/YourTenant/InventoryByLocation?\$filter=QtyOnHand gt 100&\$format=json" \
  -H "Accept: application/json" | jq '.value | length'
```

## Data Mapping

### Field Mapping Reference

| GI Result Column | REST Response Field | OData Response Field | Type | Gotcha |
|---|---|---|---|---|
| Inventory ID (InventoryItem.InventoryCD) | `ItemID.value` | `ItemID` | String | REST wraps in `{value: "..."}` object; OData returns plain value |
| Warehouse (INSite.SiteCD) | `WarehouseID.value` | `WarehouseID` | String | Field name in API 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 string; OData: `/Date(timestamp)/` format (v3) |
| Boolean/Checkbox fields | `IsActive.value` | `IsActive` | Boolean | OData filter: use lowercase `true`/`false` or `0`/`1`, not `TRUE`/`FALSE` |
| Custom field (USR prefix) | `UsrCustomField.value` | `UsrCustomField` | Varies | Custom fields require USR prefix in GI definition; API name matches |

### Data Type Gotchas

- **REST wraps every field in a `{value: "..."}` object**: Unlike standard OData which returns plain values, the Acumatica contract-based REST API wraps every field value in a `{"value": "actual_value"}` object. Your deserialization code must unwrap this layer. [src1, src7]
- **OData v3 DateTime format uses legacy `/Date()/` notation**: OData v3 GI feeds return dates as `/Date(1234567890000)/` (milliseconds since epoch), not ISO 8601. Parse accordingly. [src2]
- **Decimal precision varies by GI field type**: Currency fields return 4 decimal places; quantity fields return 6. The REST API returns these as strings, not numbers, to preserve precision. [src1]
- **Boolean fields in OData filters must be lowercase**: Use `$filter=IsActive eq true` (not `TRUE` or `True`). Alternatively, use `1` or `0`. [src4]

## Error Handling & Failure Points

### Common Error Codes

| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 Unauthorized | Authentication failed | Invalid credentials, expired token, or session timeout | Re-authenticate; check OAuth token expiry; verify credentials [src7] |
| 403 Forbidden | Insufficient permissions | User lacks access to GI or underlying DACs | Grant GI access rights and entity-level permissions in Acumatica security roles [src2] |
| 404 Not Found | Endpoint/entity not found | Wrong endpoint name, version, or GI entity name | Verify endpoint extension includes the GI entity; check URL spelling and version [src1] |
| 429 Too Many Requests | Rate limit exceeded | Exceeded per-minute API request limit or concurrent session cap | Implement backoff; reduce request frequency; check SM604000 for current limits [src5] |
| 500 Internal Server Error | GI execution failure | BQL query error, timeout, or missing DAC permissions | Check GI runs manually in UI; review Acumatica trace logs for BQL errors [src1] |
| `<NEW>` record returned | Used GET instead of PUT | GET on GI entity returns default/blank record (by design) | Switch to PUT method with empty body `{}` and `$expand=Result` [src1, src6] |
| Empty Result array | GI has no matching data or endpoint misconfigured | Results sub-entity not mapped correctly, or GI conditions exclude all records | Verify GI returns data in UI; check endpoint entity has Results sub-entity with fields [src1] |

[src1, src5, src7]

### Failure Points in Production

- **Session exhaustion from unauthenticated login loops**: If your integration creates a new session for every API call without logging out, you will exhaust the concurrent session limit (3-6) within seconds. Fix: `Always reuse session cookies across requests and call /entity/auth/logout when done. Better yet, use OAuth 2.0 bearer tokens which don't consume session slots the same way.` [src5, src7]
- **GI timeout on large result sets**: Generic Inquiries that join many tables or return >10,000 rows can timeout at the server level (~10 min default). Fix: `Add GI parameters to pre-filter data server-side. Use OData $top/$skip pagination to retrieve in chunks. Optimize the GI's BQL by adding appropriate indexes.` [src1]
- **Endpoint version mismatch after Acumatica upgrade**: After upgrading Acumatica (e.g., 23.200.001 to 24.200.001), the old endpoint version in your integration URL stops working. Fix: `Include the endpoint version in your integration config (not hardcoded). After upgrade, re-extend the endpoint for the new version and update the URL.` [src1, src7]
- **OData feed silently returns stale data when GI definition changes**: If someone modifies the GI (adds/removes columns, changes joins), the OData feed may return errors or missing fields without clear indication. Fix: `Version-control GI definitions via Acumatica customization projects. Test OData output after any GI modification.` [src2]
- **Boolean filter values rejected with syntax error**: Using `$filter=IsActive eq TRUE` (uppercase) on OData v3 GI feeds fails. Fix: `Use lowercase boolean values: $filter=IsActive eq true, or numeric: $filter=IsActive eq 1.` [src4]

## Anti-Patterns

### Wrong: Using GET to retrieve GI data via REST

```bash
# 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

```bash
# 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

```python
# BAD — creates a 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/MyEndpoint/24.200.001/GI?$expand=Result",
                         json={"ItemID": {"value": item_id}})
    # Never logs out — session slot consumed until timeout
```

### Correct: Reuse session and logout when done

```python
# GOOD — single session, reused across all requests, 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/MyEndpoint/24.200.001/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

```bash
# BAD — $skip/$top do not work on nested Result collections
curl -s -X PUT \
  "https://instance.acumatica.com/entity/MyEndpoint/24.200.001/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

```bash
# 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` on REST GI calls**: Without this parameter, the PUT request returns only the top-level entity fields (usually empty/default values), not the actual GI data rows. The Result array is simply omitted from the response. Fix: `Always append ?$expand=Result to the REST GI URL.` [src1]
- **Mapping GI fields at the top entity level instead of Results sub-entity**: When extending the endpoint at SM207000, if you add GI result fields directly under the top-level entity instead of creating a Results sub-entity first, the fields will not populate. Fix: `Create the entity hierarchy as: TopEntity > Result (sub-entity) > Fields. The Results level is mandatory.` [src1]
- **Using wrong ObjectName for the Results sub-entity**: The ObjectName for the Results sub-entity must be unique and different from the top-level entity name. Using the same name causes schema conflicts. Fix: `Use a distinct name like "InvByLocation" for the Results ObjectName, separate from the top entity name "InventoryByLocation".` [src1]
- **Not checking "Expose via OData" checkbox**: Creating a GI does not automatically make it available via OData. You must explicitly check the box on SM208000. Fix: `Open the GI form, check "Expose via OData", and save. No deployment needed.` [src2]
- **DateTime filter syntax errors in OData v3**: OData v3 DateTime filters require specific formatting that differs from v4. Fix: `Use the format: $filter=LastModified gt datetime'2024-01-01T00:00:00'. Note the datetime prefix (not datetimeoffset).` [src4]
- **Ignoring the API login limit error**: When you see "API Login Limit" errors, the problem is not rate limiting but concurrent session exhaustion. Fix: `Reduce concurrent sessions, implement proper logout calls, or switch to OAuth 2.0 which manages sessions differently.` [src5]

## Diagnostic Commands

```bash
# 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 (No Content = success)

# Verify GI entity exists in REST endpoint
curl -s "https://yourinstance.acumatica.com/entity/MyEndpoint/24.200.001/\$adoc" \
  -b cookies.txt | grep -i "InventoryByLocation"

# 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 (requires admin access to SM604000)
# Navigate to: SM604000 in Acumatica UI > Statistics tab
# Shows: Max API Users, Max Requests/Min, Current Usage
```

## 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 (`/t/{tenant}/api/odata/gi/`) | Old URL format still works; new format recommended [src2] |
| 23.200.001 (2023 R2) | 2023-10 | Supported | OData GI parameter filtering added | GIs with parameters can now be filtered via OData (previously endpoint-only) [src4] |
| 23.100.001 (2023 R1) | 2023-04 | Supported | OAuth 2.0 connected applications (SM303010) | New auth flow; cookie-based still supported [src2] |
| 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 where GI endpoint extension became available [src1] |

[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. The contract-based API schema is versioned per release (e.g., 24.200.001), and 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 to expose custom cross-table query results already defined as a GI | Need CRUD operations (create/update/delete records) | 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, complex types) | OData v4 DAC-based access (bypasses GI) |
| Result set is under 10,000 rows with reasonable query complexity | Need to extract millions of records for data warehouse | Acumatica's built-in data export or direct database replication |
| Need pagination support for large result sets | Need pagination on REST endpoint (only works on OData) | OData GI feed with $top/$skip |

## Important Caveats

- The REST contract-based approach for GIs (PUT with $expand=Result) is a workaround for Acumatica's screen-based API design — it mimics "opening the screen and clicking refresh." This means performance characteristics depend on the GI's underlying BQL complexity, not just the API call overhead. [src1, src6]
- OData v3 GI feeds have limited operator support compared to full OData v4 — complex expressions like `$apply`, `$compute`, and `substringof()` may not work. Test your specific filter syntax before relying on it in production. [src2]
- Rate limits displayed in SM604000 are your license entitlements, not hard server limits — exceeding them queues requests rather than immediately rejecting them. But if the queue fills, subsequent requests are declined with 429 errors. [src5]
- GI OData feeds bypass Acumatica's standard API logging — OData requests may not appear in the Web Services Request Statistics. Monitor via IIS logs or application-level logging instead. [src2]
- Acumatica version upgrades can break extended endpoints — always test GI API access in a sandbox after upgrading, and be prepared to re-extend endpoints for the new API version. [src1, src7]

## Related Units

- [Acumatica contract-based REST API capabilities and rate limits](/business/erp-integration/acumatica-rest-api/2026)
- [Acumatica OData v4 DAC-based data access](/business/erp-integration/acumatica-odata-v4/2026)
