Epicor Kinetic REST API v2: Capabilities, OData Patterns, and BAQ Exposures

Type: ERP Integration System: Epicor Kinetic (REST API v2) Confidence: 0.88 Sources: 8 Verified: 2026-03-02 Freshness: 2026-03-02

TL;DR

System Profile

Epicor Kinetic (formerly Epicor ERP 10) is a mid-market manufacturing ERP system. REST API v2 was introduced in Epicor 10.2.700 (2020) and became the standard API surface in Kinetic 2022.2. It replaces the legacy v1 REST API and WCF SOAP services. The v2 API adds OData v4 compliance, mandatory Company-scoped URLs, and API Key enforcement. This card covers all deployment models: cloud (Epicor SaaS), on-premise, and hybrid. Cloud/SaaS deployments have restrictions on server-side configuration (e.g., cannot modify web.config). [src1]

PropertyValue
VendorEpicor
SystemEpicor Kinetic (ERP 10.2.700+)
API SurfaceREST/OData v4
Current API Versionv2 (Kinetic 2022.2 through 2025.x)
Editions CoveredAll -- Cloud (SaaS), On-Premise, Hybrid
DeploymentCloud / On-Premise / Hybrid
API DocsEpicor REST Help (per-instance Swagger)
StatusGA

API Surfaces & Capabilities

Epicor Kinetic exposes four distinct service types through the REST API v2, but only Business Objects (BO) and BAQ Services support full OData query capabilities. Library, Process, and Report services are limited to custom method invocation via RPC-style calls. [src1]

API SurfaceProtocolBest ForOData SupportReal-time?Endpoint Pattern
Business Objects (BO)HTTPS/JSON (OData v4)CRUD on standard ERP entitiesFull ($select, $filter, $expand, $orderby, $top, $skip)Yes/api/v2/odata/{Co}/Erp.BO.{Svc}/{EntitySet}
BAQ Services (BaqSvc)HTTPS/JSON (OData v4)Custom queries, reportingFull ($select, $filter, $orderby, $top, $skip)Yes/api/v2/odata/{Co}/BaqSvc/{BaqName}/Data
Epicor Functions (EFx)HTTPS/JSON (RPC)Custom serverless logicNo (POST only)Yes/api/v2/efx/{Co}/{Library}/{Function}/
Library/Process/ReportHTTPS/JSON (RPC)Custom methods, batch processesNo (custom methods only)Varies/api/v2/odata/{Co}/Ice.{Type}.{Svc}/{Method}

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Default max rows per query100OData queries (BO and BAQ)Configurable via DefaultMaxRowCount in web.config; set to 0 for unlimited (on-premise only) [src1]
Max content length~4 GBHTTP response bodyPractical limit; large responses may timeout [src5]
ServerFileDownload limit2 GBFile download endpointUse for large BAQ exports in cloud environments [src5]
Max batch file sizeNo documented limitOData $batchEach subrequest processed independently

Rolling / Daily Limits

Limit TypeValueWindowNotes
API rate limitingNone built-inN/AMust configure at IIS or reverse proxy [src6]
Concurrent connectionsServer-dependentPer-IIS instanceControlled by IIS application pool settings [src6]
Web Services licensingLicense-based throttlingPer-sessionEpicor progressively doubles response delay when WS licenses exhausted [src6]
Session timeoutConfigurablePer-sessionOrphaned sessions consume server resources

Authentication

Epicor Kinetic REST API v2 uses a dual-layer authentication model: every request requires an API Key for access scope control, plus a user identity mechanism. [src1, src2]

FlowUse WhenToken LifetimeRefresh?Notes
Basic Auth + API KeySimple integrations, testingPer-requestN/ACredentials sent every request [src1]
Bearer Token + API KeyProduction integrationsDefault ~1h, configurableNo refresh; request new before expiryPOST to TokenResource.svc [src4]
Azure AD + API KeyCloud/SaaS enterprise SSOAzure AD token lifetimeYes (via Azure AD)Cloud only [src3]
Epicor IdP + API KeyCloud/SaaS with Epicor identityIdP token lifetimeYes (via IdP)Cloud only; MFA support [src3]
Windows SSO + API KeyOn-premise Active DirectoryWindows sessionN/AOn-premise only [src1]

Authentication Gotchas

Constraints

Integration Pattern Decision Tree

START -- Integrate with Epicor Kinetic REST API v2
|-- What service type do you need?
|   |-- Standard ERP entity CRUD (Customers, Parts, Orders)
|   |   --> Business Object (BO) endpoint with OData
|   |   |-- Read single record? --> GET /{Co}/Erp.BO.{Svc}/{Entity}('{Key}')
|   |   |-- Read with filters? --> GET with $filter, $select, $top, $skip
|   |   |-- Create/Update/Delete? --> POST / PATCH / DELETE
|   |   |-- Custom method? --> POST /{Co}/Erp.BO.{Svc}/{Method}
|   |-- Custom query / reporting?
|   |   --> BAQ Service (BaqSvc) endpoint
|   |   |-- Simple read? --> GET /{Co}/BaqSvc/{BAQ}/Data
|   |   |-- Parameterized? --> GET .../{BAQ}/Data?param1='value1'
|   |   |-- Need to write back? --> Updatable BAQ with PATCH
|   |-- Custom logic / serverless function?
|   |   --> EFx endpoint: POST /efx/{Co}/{Library}/{Function}/
|   |-- Batch process or report?
|       --> Library/Process/Report service (RPC only)
|-- What volume?
|   |-- < 100 records --> Single OData query
|   |-- 100-10,000 records --> Paginate with $top + $skip
|   |-- > 10,000 records --> ServerFileDownload or chunked pagination
|-- Authentication?
|   |-- On-premise, simple --> Basic Auth + API Key
|   |-- On-premise, production --> Bearer Token + API Key
|   |-- Cloud/SaaS --> Azure AD or Epicor IdP + API Key
|   |-- High-volume batch --> Bearer Token (reduce credential exchange)

Quick Reference

OperationMethodEndpoint PatternNotes
List BO recordsGET/api/v2/odata/{Co}/Erp.BO.CustomerSvc/CustomersDefault 100 rows
Get single recordGET/api/v2/odata/{Co}/Erp.BO.CustomerSvc/Customers('{CustID}',{CustNum})Composite key
Create recordPOST/api/v2/odata/{Co}/Erp.BO.CustomerSvc/CustomersJSON body
Update recordPATCH/api/v2/odata/{Co}/Erp.BO.CustomerSvc/Customers('{CustID}',{CustNum})Changed fields only
Delete recordDELETE/api/v2/odata/{Co}/Erp.BO.CustomerSvc/Customers('{CustID}',{CustNum})204 on success
Invoke BO methodPOST/api/v2/odata/{Co}/Erp.BO.CustomerSvc/GetByIDJSON params
Query BAQGET/api/v2/odata/{Co}/BaqSvc/{BaqName}/DataOData options apply
BAQ with parametersGET/api/v2/odata/{Co}/BaqSvc/{BaqName}/Data?param1='val'Query string params
List all BAQsGET/api/v2/odata/{Co}/BaqSvcService document
BAQ metadataGET/api/v2/odata/{Co}/BaqSvc/{BaqName}/$metadataEDMX schema
Call EFx functionPOST/api/v2/efx/{Co}/{Library}/{Function}/JSON body params
Environment infoGET/api/v2/environmentCompanies and plants
Service metadataGET/api/v2/odata/{Co}/Erp.BO.{Svc}/$metadataFull EDMX
Get bearer tokenPOST/{Instance}/TokenResource.svcBasic Auth headers

Step-by-Step Integration Guide

1. Configure API Key in Epicor

Create an API Key via System Setup > Security Maintenance > API Key Maintenance. Assign an Access Scope and copy the generated key. [src1]

Epicor Kinetic steps:
1. System Setup > Security Maintenance > API Key Maintenance
2. Click "New" to create a new API Key
3. Set description and assign an Access Scope
4. Save and copy the generated API Key value

Verify: Navigate to https://{server}/{instance}/api/v2/odata/{Company}/Erp.BO.CompanySvc/Companies?api-key={YourKey} in a browser with active Epicor session.

2. Obtain a Bearer Token

POST to TokenResource.svc with Basic Auth credentials to receive a JWT. [src4]

curl -s -X POST \
  "https://yourserver.com/EpicorInstance/TokenResource.svc" \
  -H "username: YourEpicorUser" \
  -H "password: YourPassword" \
  -H "Accept: application/json"

# Response: {"AccessToken":"eyJ...","ExpiresIn":3600,"TokenType":"Bearer"}

Verify: Decode JWT at jwt.io; exp claim should be in the future.

3. Query a Business Object with OData

Use bearer token and API Key to query with OData filtering. [src1]

curl -s "https://yourserver.com/EpicorInstance/api/v2/odata/EPIC06/Erp.BO.CustomerSvc/Customers?\$top=10&\$select=CustID,Name,City&\$filter=State%20eq%20'CA'" \
  -H "Authorization: Bearer eyJ..." \
  -H "x-api-key: your-api-key" \
  -H "Accept: application/json"

Verify: Response contains @odata.context and value array with matching records.

4. Query a BAQ (Business Activity Query)

Access a BAQ via BaqSvc. Parameters are passed as query strings. [src1]

curl -s "https://yourserver.com/EpicorInstance/api/v2/odata/EPIC06/BaqSvc/zCustomerOrders/Data?StartDate='2026-01-01'" \
  -H "Authorization: Bearer eyJ..." \
  -H "x-api-key: your-api-key" \
  -H "Accept: application/json"

Verify: Response contains BAQ-specific metadata and value array.

5. Call an Epicor Function (EFx)

Invoke custom logic via POST to the EFx endpoint. [src1, src7]

curl -s -X POST \
  "https://yourserver.com/EpicorInstance/api/v2/efx/EPIC06/MyLibrary/CalculatePrice/" \
  -H "Authorization: Bearer eyJ..." \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"partNum": "WIDGET-001", "quantity": 100}'

Verify: Response returns output parameters. 404 means function not published (try /staging/ path).

6. Implement pagination for large result sets

Use $top and $skip to paginate beyond the DefaultMaxRowCount limit. [src1]

import requests

page_size = 100
offset = 0
all_records = []
while True:
    url = f"{server}/api/v2/odata/{company}/Erp.BO.PartSvc/Parts?$top={page_size}&$skip={offset}"
    response = requests.get(url, headers=headers)
    records = response.json().get("value", [])
    if not records:
        break
    all_records.extend(records)
    offset += page_size

Verify: len(all_records) matches expected total from Epicor.

Code Examples

Python: Query BAQ with authentication and pagination

# Input:  Epicor server details, API key, credentials, BAQ name
# Output: All BAQ records with automatic pagination

import requests

server = "https://yourserver.com"
instance = "EpicorInstance"
company = "EPIC06"
api_key = "your-api-key"

# Get bearer token
token_resp = requests.post(
    f"{server}/{instance}/TokenResource.svc",
    headers={"username": "apiuser", "password": "apipass", "Accept": "application/json"}
)
access_token = token_resp.json()["AccessToken"]

headers = {
    "Authorization": f"Bearer {access_token}",
    "x-api-key": api_key,
    "Accept": "application/json"
}

baq_name = "zOpenSalesOrders"
page_size, offset, all_rows = 100, 0, []
while True:
    url = f"{server}/{instance}/api/v2/odata/{company}/BaqSvc/{baq_name}/Data?$top={page_size}&$skip={offset}"
    rows = requests.get(url, headers=headers).json().get("value", [])
    if not rows:
        break
    all_rows.extend(rows)
    offset += page_size

print(f"Retrieved {len(all_rows)} records")

JavaScript/Node.js: Business Object CRUD

// Input:  Epicor server details, API key, credentials
// Output: Filtered customer records

const fetch = require("node-fetch");
const server = "https://yourserver.com/EpicorInstance";
const company = "EPIC06";
const apiKey = "your-api-key";

async function getToken(user, pass) {
  const resp = await fetch(`${server}/TokenResource.svc`, {
    method: "POST",
    headers: { username: user, password: pass, Accept: "application/json" }
  });
  return (await resp.json()).AccessToken;
}

async function queryCustomers(token, filter) {
  const url = `${server}/api/v2/odata/${company}/Erp.BO.CustomerSvc/Customers`
    + `?$top=10&$select=CustID,Name,City&$filter=${encodeURIComponent(filter)}`;
  const resp = await fetch(url, {
    headers: { Authorization: `Bearer ${token}`, "x-api-key": apiKey, Accept: "application/json" }
  });
  return resp.json();
}

(async () => {
  const token = await getToken("apiuser", "apipass");
  const result = await queryCustomers(token, "State eq 'CA'");
  console.log(`Found ${result.value.length} customers`);
})();

cURL: Quick API exploration

# Get bearer token
TOKEN=$(curl -s -X POST "https://yourserver.com/EpicorInstance/TokenResource.svc" \
  -H "username: apiuser" -H "password: apipass" -H "Accept: application/json" \
  | python -c "import sys,json; print(json.load(sys.stdin)['AccessToken'])")

# List available companies
curl -s "https://yourserver.com/EpicorInstance/api/v2/environment" \
  -H "Authorization: Bearer $TOKEN" -H "x-api-key: your-key" | python -m json.tool

# Query parts with OData filter
curl -s "https://yourserver.com/EpicorInstance/api/v2/odata/EPIC06/Erp.BO.PartSvc/Parts?\$top=5&\$select=PartNum,PartDescription" \
  -H "Authorization: Bearer $TOKEN" -H "x-api-key: your-key" -H "Accept: application/json" | python -m json.tool

Data Mapping

OData Query Options Reference

OData OptionSyntaxExampleNotes
$selectComma-delimited$select=CustID,Name,CityReduces payload size
$filterOData expressions$filter=State eq 'CA'eq, ne, gt, ge, lt, le, and, or, not
$orderbyField + direction$orderby=Name ascasc (default) or desc
$topInteger$top=50Max rows; default limit is 100
$skipInteger$skip=100Offset for pagination
$expandNavigation property$expand=ShipToesInclude child entities
$countBoolean$count=trueReturns total count

$filter String Functions

FunctionSyntaxExample
containscontains(Field,'value')$filter=contains(Name,'Steel')
startswithstartswith(Field,'value')$filter=startswith(CustID,'CUS')
endswithendswith(Field,'value')$filter=endswith(Name,'Inc')
tolowertolower(Field)$filter=tolower(Name) eq 'acme'
year/month/dayyear(Field)$filter=year(OrderDate) eq 2026
trimtrim(Field)$filter=trim(Name) eq 'ACME'

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
401UnauthorizedWrong credentials or expired tokenVerify credentials; acquire fresh token [src4]
403Access denied: API KeyMissing or invalid API KeyAdd x-api-key header; check Access Scope [src1, src2]
404Not FoundWrong service, BAQ, or entity key nameCheck name in Swagger help; try /staging/ for EFx [src1]
405Method Not AllowedWrong HTTP verb (e.g., GET on TokenResource.svc)Use POST for tokens; GET for read-only BAQs [src4]
500Internal Server ErrorBPM error, validation failureCheck server event log; review BPM code [src1]
Missing Root ElementMalformed JSONSmart/curly quotes in JSON bodyUse straight quotes; validate JSON [src7]

Failure Points in Production

Anti-Patterns

Wrong: Using Basic Auth for every request in high-volume integration

# BAD -- sends credentials every request, creates new session each time
for part_num in part_numbers:
    response = requests.get(url, auth=HTTPBasicAuth(user, pwd), headers={"x-api-key": key})

Correct: Acquire bearer token once, reuse for all requests

# GOOD -- single auth, reuse token and session
token = get_bearer_token(server, user, pwd)
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {token}", "x-api-key": key})
for part_num in part_numbers:
    response = session.get(url)

Wrong: Fetching all records without pagination

# BAD -- only gets first 100 records, silently misses the rest
customers = requests.get(f"{server}/.../Customers", headers=h).json()["value"]

Correct: Paginate through all records

# GOOD -- retrieves all records with explicit pagination
all_data, offset = [], 0
while True:
    batch = requests.get(f"{url}?$top=100&$skip={offset}", headers=h).json().get("value", [])
    if not batch: break
    all_data.extend(batch)
    offset += 100

Wrong: Using v1 URL pattern for new integrations

# BAD -- v1 lacks Company scoping, no OData, deprecated
url = f"{server}/api/v1/Erp.BO.CustomerSvc/Customers"

Correct: Use v2 URL with Company in path

# GOOD -- v2 with OData, Company-scoped, API Key enforced
url = f"{server}/api/v2/odata/{company}/Erp.BO.CustomerSvc/Customers"

Common Pitfalls

Diagnostic Commands

# Check API connectivity and list companies
curl -s "https://yourserver.com/EpicorInstance/api/v2/environment" \
  -H "Authorization: Bearer $TOKEN" -H "x-api-key: $APIKEY" | python -m json.tool

# Verify authentication
curl -s "https://yourserver.com/EpicorInstance/api/v2/odata/EPIC06/Erp.BO.CompanySvc/Companies?\$top=1" \
  -H "Authorization: Bearer $TOKEN" -H "x-api-key: $APIKEY" | python -m json.tool

# List all available BAQs
curl -s "https://yourserver.com/EpicorInstance/api/v2/odata/EPIC06/BaqSvc" \
  -H "Authorization: Bearer $TOKEN" -H "x-api-key: $APIKEY" | python -m json.tool

# Get BAQ metadata/schema
curl -s "https://yourserver.com/EpicorInstance/api/v2/odata/EPIC06/BaqSvc/YourBaq/\$metadata" \
  -H "Authorization: Bearer $TOKEN" -H "x-api-key: $APIKEY"

# Get BO service metadata
curl -s "https://yourserver.com/EpicorInstance/api/v2/odata/EPIC06/Erp.BO.PartSvc/\$metadata" \
  -H "Authorization: Bearer $TOKEN" -H "x-api-key: $APIKEY"

Version History & Compatibility

API VersionReleaseStatusKey ChangesMigration Notes
REST v2 (Kinetic 2024.x)2024-H1CurrentEFx staging endpoint, Cloud SDK UD servicesUse /staging/ for unpublished EFx
REST v2 (Kinetic 2022.2)2022-H2Supportedv2 became default; v1 deprecatedAll new integrations should use v2
REST v2 (10.2.700)2020SupportedOData v4, Company in URL, API Key, JSON-onlyAdd /odata/{Company}/ and API Key
REST v1 (10.2.x)2017-2020DeprecatedOriginal REST; XML/Atom supportedReplace /api/v1/ with /api/v2/odata/{Co}/
WCF/SOAPLegacyMaintenance-onlyOriginal service contractsMigrate to REST v2

Deprecation Policy

Epicor does not publish a formal API deprecation timeline. REST v1 and WCF services continue to function but receive no new features. Epicor's strategy is REST v2 + EFx as the sole integration surface. Monitor Epicor release notes and EpicWeb for deprecation notices. [src1]

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Real-time CRUD on standard Epicor BOsMigrating >100K records in a single batchServerFileDownload or DMT (Data Migration Tool)
Exposing BAQ data to BI/dashboardsNeed offline access to full Epicor datasetEpicor Data Analytics (EDA) or GROW BI
Custom logic via EFx functionsDirect database modificationsAlways use REST API or BPM -- never write to DB directly
Integrating cloud Epicor with third-party systemsSub-millisecond latency requirementsAdd a caching layer in front of Epicor API
Building custom web/mobile appsGenerating SSRS reports programmaticallyIce.RPT service types via REST API

Important Caveats

Related Units