Epicor BAQ API Endpoints: Exposing Business Activity Queries via REST/OData
How do you expose Epicor Business Activity Queries (BAQ) as API endpoints?
TL;DR
- Bottom line: Epicor BAQs are exposed as REST/OData endpoints at
/api/v2/odata/{Company}/BaqSvc/{BaqName}/Data(OData) or/api/v2/baq/{BaqName}(REST). Configure Access Scopes and API keys to control external access. [src1, src2] - Key limit: Default max row count is 100 per request; use
$topand$skipfor pagination. Cloud/SaaS users cannot override this via web.config. [src3, src8] - Watch out for: BAQ parameters filter at the database level while OData
$filterapplies post-processing — use BAQ parameters for large datasets to avoid memory issues. [src7] - Best for: Read-only data extraction, BI tool integration (Excel, Power BI, Grafana), and custom application data access from Epicor ERP.
- Authentication: API key (header or query parameter), Basic Auth (username/password), or Bearer token via
TokenResource.svc(8-hour lifetime). [src1, src6]
System Profile
This card covers how to expose Epicor Business Activity Queries (BAQs) as external REST and OData API endpoints using Epicor Kinetic REST Services v2. BAQs are custom queries built in the Epicor BAQ Designer that retrieve specific data from the Epicor database — they inherit all table and field security from the Epicor security model. This card applies to both cloud/SaaS and on-premise Epicor Kinetic deployments, though certain configuration options (like web.config modifications) are only available on-premise. [src1, src4]
| Property | Value |
|---|---|
| Vendor | Epicor |
| System | Epicor Kinetic (ERP) — REST API v2 |
| API Surface | REST + OData v4 |
| Current API Version | REST v2 (Kinetic 2022.2+) |
| Editions Covered | Standard, Enterprise, Cloud/SaaS, On-Premise |
| Deployment | Cloud / On-Premise / Hybrid |
| API Docs | Swagger UI (per-instance) |
| Status | GA |
API Surfaces & Capabilities
Epicor exposes BAQ data through two primary endpoint patterns: the OData endpoint (optimized for data feeds and BI tools) and the REST endpoint (optimized for programmatic CRUD operations on updatable BAQs). Both share the same authentication infrastructure. [src1, src2, src5]
| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
OData BAQ (/odata/{Co}/BaqSvc/{BAQ}/Data) | HTTPS/JSON (OData v4) | BI tools, Excel, Power BI, Grafana feeds | 100 default ($top/$skip for more) | No hard limit; server-dependent | Yes | Via pagination |
REST BAQ (/baq/{BAQ}) | HTTPS/JSON | Programmatic access, updatable BAQ CRUD | 100 default (configurable on-prem) | No hard limit; server-dependent | Yes | No |
REST BAQ GetNew (/baq/{BAQ}/GetNew) | HTTPS/JSON | Get blank row template for updatable BAQ inserts | 1 row template | N/A | Yes | No |
REST BAQSvc (Erp.BO.BAQSvc) | HTTPS/JSON | SSRS report submission, advanced BAQ operations | Varies | N/A | Yes | No |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Default max row count | 100 | All BAQ REST/OData endpoints | Use $top and $skip to paginate beyond default [src8] |
| Max file download size | 2 GB | ServerFileDownload endpoint | System.IO limitation [src3] |
| Max allowed content length | ~4 GB | All REST endpoints | Configurable via MaxAllowedContentLength in web.config (on-prem only) [src3] |
| Max batch size (updatable) | 1 row per PATCH | Updatable BAQ updates | Pass one changed row at a time to the Data method [src5] |
Rolling / Daily Limits
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API call limit | No explicit per-day limit | N/A | Epicor does not publish formal API rate limits — server resources are the practical constraint [src3] |
| Concurrent sessions | Tied to Epicor license count | Per-instance | Each API session consumes a CAL (Client Access License) [src5] |
| DefaultMaxRowCount override | Configurable (0 = unlimited) | Per-instance | On-premise only; cloud/SaaS cannot modify [src3] |
Authentication
Epicor REST API supports three authentication methods. All three work for BAQ endpoints. API key authentication is the recommended approach for service-to-service integrations. [src1, src6]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| API Key | Service integrations, external apps, BI tools | No expiry (until rotated) | N/A | Header: X-API-Key or query parameter ?api-key={key}. Created in API Key Maintenance. |
| Basic Authentication | Quick testing, internal tools | Session-based | N/A | Authorization: Basic {base64(user:pass)}. Each request creates a new session. |
| Bearer Token | Long-running sessions, server-to-server | 8 hours (28,800s) | No — acquire new token | POST to TokenResource.svc with username/password headers. Returns JWT. |
Authentication Gotchas
- API keys must be paired with Access Scopes: Creating an API key alone is insufficient — you must create an Access Scope that includes
Erp.BO.BAQSvcin the services list AND add the specific BAQ name to the BAQs list. Without this, the API returns 403. [src1] - API key is shown only once at creation: After saving the API key in Epicor, the key value is never shown again. Copy it immediately and store securely. [src1]
- Bearer tokens still require API key headers: Even when using Bearer token authentication, you may still need to send the
X-API-Keyheader on subsequent requests. [src6] - Sessions consume licenses: Each authenticated API session (especially Basic Auth) consumes a Client Access License (CAL). Close sessions when done, or use API key auth which shares a session. [src5]
Constraints
- Cloud/SaaS customers cannot modify
DefaultMaxRowCount— the 100-row default per request is effectively fixed; pagination via$top/$skipis mandatory for larger result sets. [src3] - OData
$filteris post-processing, not database-level — filtering happens after the BAQ query executes, meaning the full result set is loaded into memory first. For large BAQs, use BAQ parameters instead. [src7] - Updatable BAQs accept one row per PATCH — you cannot batch multiple row updates in a single request. Loop through rows individually. [src5]
- Excel strips query parameters from OData URLs — when connecting Excel to OData v2 endpoints, Excel's native connector removes query strings including
?api-key=, forcing fallback to Basic Auth. [src2] - BAQ security inherits from Epicor table/field security — the API cannot bypass security configured in the BAQ Designer or at the Epicor security level. [src4]
- REST v1 and v2 coexist — v1 uses
/api/help/v1/and v2 uses/api/help/v2/. Some older documentation references v1 endpoints which still work but lack OData query parameter support. [src4]
Integration Pattern Decision Tree
START — User needs to expose Epicor BAQ as API endpoint
|-- What's the use case?
| |-- Read-only data extraction (reporting, dashboards)
| | |-- BI tool (Excel, Power BI, Grafana)?
| | | |-- YES --> OData endpoint: /api/v2/odata/{Co}/BaqSvc/{BAQ}/Data
| | | | |-- Excel --> Use Basic Auth (Excel strips API key from URL) [src2]
| | | | |-- Power BI --> Use API key in header or Basic Auth
| | | | |-- Grafana --> Use API key as query parameter [src2]
| | | |-- NO --> REST endpoint: /api/v2/baq/{BAQ}
| | |-- Need filtering?
| | |-- Small dataset (<10K rows) --> OData $filter (post-processing) [src7]
| | |-- Large dataset (>10K rows) --> BAQ parameters (DB-level) [src7]
| |-- Updatable BAQ (CRUD operations)
| | |-- Create new record --> POST to /api/v2/baq/{BAQ}/GetNew, then PATCH with data [src5]
| | |-- Update existing record --> PATCH to /api/v2/baq/{BAQ}/Data [src5]
| | |-- Delete record --> DELETE to /api/v2/baq/{BAQ}/Data
| |-- SSRS report generation via BAQ
| --> POST to Ice.RPT.BAQReportSvc/SubmitToAgent [src5]
|-- Which authentication?
| |-- Service-to-service --> API key (no session overhead) [src1]
| |-- User-context --> Basic Auth or Bearer token [src6]
| |-- Long-running process --> Bearer token (8h lifetime) [src6]
|-- Pagination needed?
|-- < 100 rows --> No pagination needed
|-- > 100 rows --> Use $top and $skip [src8]
Quick Reference
| Operation | Method | Endpoint | Auth Header | Notes |
|---|---|---|---|---|
| Execute BAQ (OData) | GET | /api/v2/odata/{Company}/BaqSvc/{BaqName}/Data | X-API-Key: {key} | Returns OData-formatted JSON |
| Execute BAQ (REST) | GET | /api/v2/baq/{BaqName} | X-API-Key: {key} | Returns standard JSON |
| Execute BAQ with params | GET | .../Data?Param1=Value1 | X-API-Key: {key} | BAQ parameters as query params |
| Filter results (OData) | GET | .../Data?$filter=Field eq 'Value' | X-API-Key: {key} | Post-processing filter |
| Paginate results | GET | .../Data?$top=100&$skip=200 | X-API-Key: {key} | Third page of 100 rows |
| Select fields | GET | .../Data?$select=Field1,Field2 | X-API-Key: {key} | Reduce payload size |
| Get blank row (updatable) | GET | /api/v2/baq/{BaqName}/GetNew | X-API-Key: {key} | Returns template row for insert |
| Update row (updatable) | PATCH | /api/v2/baq/{BaqName}/Data | X-API-Key: {key} | Pass single row as {"ds": {...}} |
| Get Bearer token | POST | /{instance}/TokenResource.svc | username + password headers | Returns JWT valid for 8 hours |
| Access Swagger help | GET | /api/help/v2/ | Browser session | Interactive API docs |
Step-by-Step Integration Guide
1. Build and save the BAQ in Epicor BAQ Designer
Create your Business Activity Query in the Epicor Kinetic UI. Define the tables, joins, calculated fields, and any parameters you need. Mark the BAQ as "shared" if external applications need to access it. [src1, src4]
Epicor Kinetic steps:
1. Open BAQ Designer (System Management > Business Activity Queries)
2. Create new BAQ or open existing
3. Define query tables, fields, joins, and criteria
4. For parameterized BAQs: add parameters on the Parameters tab
5. For updatable BAQs: enable "Updatable" flag on the General tab
6. Test with "Test" button
7. Save — note the BAQ ID (e.g., "MyCustomBAQ")
Verify: Run the BAQ in the designer and confirm it returns expected results.
2. Create an Access Scope for the BAQ
Access Scopes control which services and BAQs are available to API consumers. Navigate to System Setup > Security Maintenance > Access Scope Maintenance. [src1]
Access Scope setup:
1. Navigate to System Setup > Security Maintenance > Access Scope Maintenance
2. Create a new scope with an ID (e.g., "ExternalBAQAccess")
3. In the Services list, confirm "Erp.BO.BAQSvc" is listed
4. In the BAQs list, add your specific BAQ name (e.g., "MyCustomBAQ")
5. Save the Access Scope
Verify: The Access Scope shows Erp.BO.BAQSvc in services and your BAQ name in the BAQs list.
3. Generate an API key bound to the Access Scope
Create an API key that uses the Access Scope from step 2. [src1]
API Key setup:
1. Navigate to System Setup > Security Maintenance > API Key Maintenance
2. Create a new API Key
3. Fill in required fields (description, user, company)
4. Select the Access Scope created in step 2
5. Save — IMMEDIATELY copy the API key value
6. Store the key in a secure external location
WARNING: The API key value is shown only once. If lost, you must create a new key.
Verify: The API key appears in the API Key Maintenance list with the correct Access Scope.
4. Call the BAQ endpoint with authentication
Use the OData or REST endpoint to retrieve BAQ data. Include the API key in the header or as a query parameter. [src1, src2]
# OData endpoint (recommended for data feeds)
curl -s "https://your-server/instance/api/v2/odata/Company/BaqSvc/MyCustomBAQ/Data" \
-H "X-API-Key: your-api-key-here" \
-H "Accept: application/json"
# REST endpoint (alternative)
curl -s "https://your-server/instance/api/v2/baq/MyCustomBAQ" \
-H "X-API-Key: your-api-key-here" \
-H "Accept: application/json"
# With BAQ parameter
curl -s "https://your-server/instance/api/v2/odata/Company/BaqSvc/MyCustomBAQ/Data?CustomerID=ACME001" \
-H "X-API-Key: your-api-key-here" \
-H "Accept: application/json"
Verify: Response returns JSON with a value array containing BAQ result rows. HTTP status 200.
5. Implement pagination for large result sets
The default max row count is 100. Use $top and $skip OData parameters to paginate through larger result sets. [src8]
# Page 1 (rows 0-99)
curl -s ".../BaqSvc/MyCustomBAQ/Data?$top=100&$skip=0" \
-H "X-API-Key: your-api-key-here"
# Page 2 (rows 100-199)
curl -s ".../BaqSvc/MyCustomBAQ/Data?$top=100&$skip=100" \
-H "X-API-Key: your-api-key-here"
Verify: Each page returns up to 100 rows. When fewer rows than $top are returned, you have reached the end.
6. Apply OData filters and field selection
Use OData query parameters to filter results and select specific fields. Remember that $filter is post-processing — for better performance, use BAQ parameters instead. [src7]
# Filter with equality
curl -s ".../Data?$filter=PartPlant_PersonID eq 'JSMITH'" \
-H "X-API-Key: your-api-key-here"
# Select specific fields only
curl -s ".../Data?$select=OrderNum,CustomerName,OrderTotal" \
-H "X-API-Key: your-api-key-here"
# Combine filter, select, and pagination
curl -s ".../Data?$filter=OrderTotal gt 1000&$select=OrderNum,CustomerName&$top=50" \
-H "X-API-Key: your-api-key-here"
Verify: Response contains only matching rows and selected fields.
Code Examples
Python: Execute BAQ with pagination
# Input: Epicor server URL, company, BAQ name, API key
# Output: All BAQ results (paginated)
import requests
SERVER = "https://your-epicor-server/YourInstance"
COMPANY = "YourCompany"
BAQ_NAME = "MyCustomBAQ"
API_KEY = "your-api-key-here"
PAGE_SIZE = 100
def get_baq_data(baq_name, params=None, page_size=PAGE_SIZE):
"""Fetch all rows from an Epicor BAQ with automatic pagination."""
url = f"{SERVER}/api/v2/odata/{COMPANY}/BaqSvc/{baq_name}/Data"
headers = {
"X-API-Key": API_KEY,
"Accept": "application/json"
}
all_rows = []
skip = 0
while True:
query_params = {"$top": page_size, "$skip": skip}
if params:
query_params.update(params)
resp = requests.get(url, headers=headers, params=query_params)
resp.raise_for_status()
data = resp.json()
rows = data.get("value", [])
all_rows.extend(rows)
if len(rows) < page_size:
break
skip += page_size
return all_rows
# Usage
results = get_baq_data(BAQ_NAME)
print(f"Total rows: {len(results)}")
# With BAQ parameter
results = get_baq_data(BAQ_NAME, params={"CustomerID": "ACME001"})
JavaScript/Node.js: Execute BAQ with API key
// Input: Epicor server URL, company, BAQ name, API key
// Output: BAQ results as JSON
const fetch = require("node-fetch"); // npm install node-fetch@2
const SERVER = "https://your-epicor-server/YourInstance";
const COMPANY = "YourCompany";
const API_KEY = "your-api-key-here";
async function getBAQData(baqName, params = {}) {
const url = new URL(
`${SERVER}/api/v2/odata/${COMPANY}/BaqSvc/${baqName}/Data`
);
Object.entries(params).forEach(([key, val]) => url.searchParams.set(key, val));
const response = await fetch(url.toString(), {
headers: {
"X-API-Key": API_KEY,
"Accept": "application/json",
},
});
if (!response.ok) {
throw new Error(`BAQ request failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return data.value || [];
}
(async () => {
const rows = await getBAQData("MyCustomBAQ", {
"$top": "100",
"$filter": "OrderTotal gt 1000",
});
console.log(`Rows returned: ${rows.length}`);
})();
cURL: Quick BAQ API test with all auth methods
# === Method 1: API Key (recommended) ===
curl -s "https://server/instance/api/v2/odata/Company/BaqSvc/MyBAQ/Data" \
-H "X-API-Key: your-api-key" \
-H "Accept: application/json" | jq .
# === Method 2: Basic Authentication ===
curl -s "https://server/instance/api/v2/odata/Company/BaqSvc/MyBAQ/Data" \
-u "username:password" \
-H "Accept: application/json" | jq .
# === Method 3: Bearer Token ===
TOKEN=$(curl -s -X POST "https://server/instance/TokenResource.svc" \
-H "username: epicor_user" \
-H "password: epicor_pass" \
-H "Accept: application/json" | jq -r '.AccessToken')
curl -s "https://server/instance/api/v2/odata/Company/BaqSvc/MyBAQ/Data" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: your-api-key" \
-H "Accept: application/json" | jq .
Data Mapping
BAQ Endpoint URL Pattern Reference
| Component | Value | Example | Notes |
|---|---|---|---|
| Base URL | https://{server}/{instance} | https://centralusdtapp01.epicorsaas.com/saas512 | Varies by deployment |
| OData BAQ path | /api/v2/odata/{Company}/BaqSvc/{BaqName}/Data | /api/v2/odata/MYCO/BaqSvc/SalesOrders/Data | Recommended for data feeds |
| REST BAQ path | /api/v2/baq/{BaqName} | /api/v2/baq/SalesOrders | Alternative format |
| REST v1 path | /api/v1/BaqSvc/{BaqName} | /api/v1/BaqSvc/SalesOrders | Legacy, still functional |
| Swagger help | /api/help/v2/ | /api/help/v2/ | Interactive docs |
| Token endpoint | /{instance}/TokenResource.svc | /saas512/TokenResource.svc | Bearer token acquisition |
Data Type Gotchas
- OData field names use Table_Field format: BAQ OData results prefix field names with the table name and underscore (e.g.,
OrderHed_OrderNum), which differs from display names. [src2] - Date fields return ISO 8601 format: All date/datetime fields are returned in ISO 8601 format regardless of the user's Epicor date format preference. [src5]
- Calculated fields use
Calculated_prefix: Any calculated columns in the BAQ appear with aCalculated_prefix in API results. [src2]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 Unauthorized | Invalid or missing authentication | API key not provided, expired, or invalid | Verify API key exists and is correctly copied; check Access Scope includes the BAQ |
| 403 Forbidden | Access denied | Access Scope does not include Erp.BO.BAQSvc or the specific BAQ | Add Erp.BO.BAQSvc to Access Scope services AND add BAQ name to BAQs list |
| 404 Not Found | BAQ or endpoint not found | BAQ name misspelled, not shared, or wrong company ID | Verify BAQ name in BAQ Designer; check company ID; use Swagger help to confirm |
| 500 Internal Server Error | Server-side error | BAQ query has SQL errors, timeout, or references deleted objects | Test BAQ in designer; check Epicor server logs; simplify query |
| Root element is missing | Malformed JSON payload | Smart quotes or encoding issues in request body | Use straight quotes in JSON; verify UTF-8 encoding without BOM |
Failure Points in Production
- API key rotation with no notification: Epicor does not send expiration warnings for API keys. If a key is rotated or deleted, integrations break silently. Fix:
Document all API keys in a secrets manager with ownership and rotation schedule. Monitor for 401 errors.[src1] - BAQ modification breaks API consumers: Editing a shared BAQ (adding/removing/renaming columns) breaks all API consumers. Fix:
Version your BAQs (e.g., SalesOrders_v1, SalesOrders_v2). Never modify a BAQ that has external consumers — create a new version.[src3] - Memory exhaustion on unfiltered large BAQs: Calling a BAQ without parameters on a large table loads the entire result into server memory. Fix:
Always add BAQ parameters for large tables. Use $top to limit initial requests.[src3, src7] - Session license exhaustion: High-frequency Basic Auth API calls can exhaust all available CAL licenses. Fix:
Use API key authentication (shares sessions). Use requests.Session() in Python to reuse sessions.[src5] - NGINX proxy update breaking BAQ APIs: Epicor SaaS infrastructure updates can alter URL handling and break existing integrations. Fix:
Monitor Epicor release notes and SaaS infrastructure announcements. Test API endpoints after each update window.[src3]
Anti-Patterns
Wrong: Using OData $filter on large datasets instead of BAQ parameters
# BAD — $filter is post-processing; the full BAQ executes first, loading
# millions of rows into memory, then filtering client-side
url = f"{SERVER}/api/v2/odata/{COMPANY}/BaqSvc/AllOrders/Data"
url += "?$filter=CustomerID eq 'ACME001'"
# This loads ALL orders into memory, then filters — slow and dangerous
Correct: Use BAQ parameters for database-level filtering
# GOOD — BAQ parameters filter at the DB level before results are loaded
url = f"{SERVER}/api/v2/odata/{COMPANY}/BaqSvc/OrdersByCustomer/Data"
url += "?CustomerID=ACME001"
# This adds a WHERE clause to the SQL, returning only matching rows
Wrong: Calling BAQ API without pagination for unknown result sizes
# BAD — assumes result fits in default 100 rows; silently truncates data
url = f"{SERVER}/api/v2/odata/{COMPANY}/BaqSvc/AllParts/Data"
resp = requests.get(url, headers=headers)
parts = resp.json()["value"] # Only first 100 rows!
Correct: Always implement pagination loop
# GOOD — fetches all pages regardless of result size
all_parts = []
skip = 0
while True:
resp = requests.get(f"{url}?$top=100&$skip={skip}", headers=headers)
rows = resp.json().get("value", [])
all_parts.extend(rows)
if len(rows) < 100:
break
skip += 100
Wrong: Modifying a shared BAQ that has external API consumers
# BAD — An Epicor admin adds a new column to "SalesOrders" BAQ.
# All Power BI reports and external integrations using field positions break.
# Renamed fields cause KeyError / undefined errors in consuming applications.
Correct: Version BAQs and maintain backward compatibility
# GOOD — Create "SalesOrders_v2" with the new column.
# Migrate consumers one at a time from v1 to v2.
# Keep v1 running until all consumers are migrated.
# Only then deprecate SalesOrders_v1.
Common Pitfalls
- Forgetting to add BAQ to Access Scope BAQs list: Creating an Access Scope with
Erp.BO.BAQSvcis necessary but not sufficient — you must also add each BAQ name to the scope's BAQs list. Fix:Open Access Scope Maintenance, navigate to the BAQs tab, and add each BAQ.[src1] - Using REST v1 URL format with v2 features: OData query parameters may not work correctly on REST v1 endpoints. Fix:
Always use v2 endpoints (/api/v2/odata/...) for OData query parameter support.[src4] - Excel stripping API key from OData URLs: Excel's OData connector strips query strings including
?api-key=. Fix:Use Basic Authentication when connecting Excel, or use Power Query with a custom connector that sets the X-API-Key header.[src2] - Not URL-encoding filter values: OData
$filtervalues with spaces or special characters must be URL-encoded. Fix:Use URL encoding libraries (urllib.parse.quote in Python, encodeURIComponent in JavaScript).[src7] - Assuming BAQ field names match UI labels: API field names use
Table_Columnformat, not display labels. Fix:Use the Swagger help page or a test API call to identify exact field names.[src2] - Not handling Epicor session/license consumption: Each Basic Auth request may create a new session, consuming a CAL license. Fix:
Use API key authentication for automated integrations.[src5]
Diagnostic Commands
# Test API key authentication against a BAQ
curl -s -o /dev/null -w "%{http_code}" \
"https://server/instance/api/v2/odata/Company/BaqSvc/MyBAQ/Data?$top=1" \
-H "X-API-Key: your-api-key"
# Expected: 200 (success), 401 (bad key), 403 (bad scope), 404 (bad BAQ name)
# Test Bearer token acquisition
curl -s -X POST "https://server/instance/TokenResource.svc" \
-H "username: epicor_user" \
-H "password: epicor_pass" \
-H "Accept: application/json" | jq '{token_type: .TokenType, expires_in: .ExpiresIn}'
# Expected: {"token_type": "Bearer", "expires_in": 28800}
# Check BAQ response structure (first row only)
curl -s "https://server/instance/api/v2/odata/Company/BaqSvc/MyBAQ/Data?$top=1" \
-H "X-API-Key: your-api-key" | jq '.value[0] | keys'
# Count total BAQ rows
curl -s "https://server/instance/api/v2/odata/Company/BaqSvc/MyBAQ/Data?$count=true&$top=0" \
-H "X-API-Key: your-api-key" | jq '.["@odata.count"]'
Version History & Compatibility
| API Version | Release Date | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| REST v2 (Kinetic 2022.2+) | 2022-09 | Current | OData query parameter support added; new URL pattern | Use /api/v2/odata/ for new integrations |
| REST v2 (Kinetic 2024.1) | 2024-03 | Current | Token endpoint standardized | TokenResource.svc endpoint for bearer tokens |
| REST v1 | 2016 | Supported (legacy) | Limited OData support | Use /api/v1/BaqSvc/ format; migration to v2 recommended |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Extracting data for dashboards, reports, or BI tools | Building complex business logic that modifies multiple related tables | Epicor Business Objects (BO) REST API or EFx Functions |
| Providing read-only data feeds to external applications | Orchestrating multi-step transactions | Epicor BPM / EFx Functions with proper transaction handling |
| Connecting Excel or Power BI to live Epicor data | Need real-time push notifications when data changes | Epicor Service Connect / BPM Event-Driven patterns |
| Simple CRUD on single-table updatable BAQs | Bulk data migration (>100K records) | Epicor DMT (Data Migration Tool) or direct database ETL |
Important Caveats
- BAQ REST endpoints inherit all Epicor table-level and field-level security — the API cannot return data the authenticated user/key does not have access to. Ensure the API key's associated user has appropriate security group assignments.
- On-premise and cloud/SaaS deployments differ significantly in configuration options: web.config tuning (DefaultMaxRowCount, MaxAllowedContentLength) is only available on-premise. Cloud customers must work within default limits using pagination.
- Epicor does not publish formal API rate limits — practical throughput depends on server hardware, concurrent users, and query complexity. Heavy BAQ API usage on shared SaaS infrastructure can trigger throttling with no prior warning.
- BAQ performance via API is directly tied to the BAQ's SQL efficiency. A poorly designed BAQ (missing indexes, full table scans, excessive joins) will be slow via API regardless of network or client optimization.
- Epicor SaaS infrastructure updates (NGINX, IIS, load balancer changes) can break BAQ API integrations without direct notification. Subscribe to Epicor EpicCare and release notes for advance warning.