Epicor Kinetic Authentication, Functions, and BPM Customization APIs
How do Epicor authentication, functions, and BPM customization APIs work?
TL;DR
- Bottom line: Epicor Kinetic provides three authentication paths: Basic auth (simplest, least secure), bearer tokens via
TokenResource.svc(recommended for integrations), and Azure AD/Epicor IdP (recommended for SaaS with MFA). REST API v2 always requires an API key header. - Key limit: Bearer tokens default to 1 hour expiry with no built-in refresh token mechanism. Token lifetime is configurable via Admin Console.
- Watch out for: REST API v2 requires an API key for every request even when using bearer tokens. Forgetting the API key header returns 401 even with a valid token.
- Best for: Any integration needing CRUD operations against Epicor Kinetic -- orders, inventory, customers, jobs, BOM -- plus custom server-side logic via Epicor Functions.
- Authentication: Basic auth (username:password per request), bearer token (
TokenResource.svc), or Azure AD / Epicor IdP (SaaS). API key required for all v2 endpoints.
System Profile
This card covers authentication, BPM customization, and Epicor Functions for Epicor Kinetic (the cloud-first rebranding of Epicor ERP 10). Kinetic runs both on-premise and as Epicor-hosted SaaS. All editions share the same REST API surface, but SaaS deployments restrict direct database access and require the Cloud SDK for custom objects. The REST API exposes every Business Object (BO), BAQ, and Function via OData-compatible endpoints. This card does NOT cover Epicor Prophet 21 (P21), Epicor for Retail, or Epicor BisTrack, which are separate product lines with different API surfaces.
| Property | Value |
|---|---|
| Vendor | Epicor |
| System | Epicor Kinetic (ERP 10+) |
| API Surface | REST (OData v4), SOAP (legacy) |
| Current API Version | v2 (introduced 2022.2; v1 still active for Kinetic UI) |
| Editions Covered | Cloud SaaS, On-Premise, Hybrid |
| Deployment | Hybrid (cloud + on-premise supported) |
| API Docs | Epicor Cloud SDK & REST Help (instance-specific) |
| Status | GA |
API Surfaces & Capabilities
Epicor Kinetic exposes multiple API surfaces for different integration needs. REST API v2 is the primary surface for external integrations, while v1 remains active for the Kinetic browser UI.
| API Surface | Protocol | Best For | Auth Methods | Real-time? | Bulk? |
|---|---|---|---|---|---|
| REST API v2 | HTTPS/JSON (OData) | External integrations, Functions | Bearer token, Basic + API key | Yes | Limited |
| REST API v1 | HTTPS/JSON (OData) | Kinetic UI, legacy integrations | Bearer token, Basic | Yes | Limited |
| Epicor Functions (EFx) | HTTPS/JSON via v2 | Custom server-side logic, reusable business rules | Bearer token + API key | Yes | No |
| BAQ REST endpoints | HTTPS/JSON (OData) | Custom queries, reporting, read-heavy workloads | Bearer token, Basic + API key | Yes | No |
| SOAP/WCF Services | HTTPS/XML | Legacy integrations only | Basic, Windows auth | Yes | No |
| OData Feeds | OData v4 | Excel, Power BI, BI tools | Basic + API key | Yes | No |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per OData query | 100 default ($top) | All REST endpoints | Use $skip for pagination; configurable via server settings |
| Max request body size | Varies by server config | POST/PATCH operations | Default IIS limit applies (~28.6 MB for on-premise) |
| Max concurrent sessions | Server-dependent | All authenticated users | Controlled by Epicor license count |
| BAQ query timeout | 30 seconds default | BAQ REST endpoints | Configurable in BAQ designer |
Rolling / Daily Limits
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API calls | No hard published limit | N/A | SaaS: Epicor may throttle excessive usage; On-premise: limited by server capacity |
| Concurrent REST connections | License-dependent | Per-server | Each connection consumes a license seat unless using a dedicated integration account |
| TokenResource.svc requests | No explicit limit | N/A | Rate-limit token requests to avoid unnecessary load |
Authentication
All Epicor Kinetic REST API v2 calls require an API key in addition to any authentication method. The API key acts as an application-level gate separate from user authentication.
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Basic Authentication | Quick testing, simple scripts, Swagger UI | N/A (per-request) | N/A | Credentials sent with every request (Base64). Only method for REST Help/Swagger UI. [src1] |
| Bearer Token (TokenResource.svc) | Server-to-server integrations, scheduled jobs | Default 1h (configurable up to 100h+) | No refresh token; request new token before expiry | POST to TokenResource.svc with username/password headers. Returns JWT. [src2, src3] |
| Azure AD / Epicor IdP | SaaS deployments, MFA-required environments | ~1h (Azure AD default) | Yes (Azure AD refresh tokens) | JWT obtained from Azure AD or Epicor IdP; passed as Bearer header. Recommended for production SaaS. [src1] |
| API Key (supplemental) | All v2 REST calls | Does not expire (until rotated) | N/A | Required in addition to any auth method above. Passed as header or query parameter. [src4] |
Authentication Gotchas
- API key is always required for v2: Even with a valid bearer token, omitting the API key header returns 401 Unauthorized. The API key acts as an application-level gate separate from user authentication. [src4]
- Swagger UI only supports Basic auth: The built-in REST Help page (
/apps/resthelp/) cannot use bearer tokens or API keys. Use Postman or custom code for testing token-based auth. [src1] - Token lifetime varies by configuration: Default is 1 hour, but admins can change it via Admin Console > Server Management > Configure Token Authentication. Some environments set it to 8 hours (28800 seconds) or higher. [src2, src3]
- No refresh token for TokenResource.svc: Unlike Azure AD, the internal token service does not issue refresh tokens. You must proactively request a new token before the current one expires. [src3]
- SaaS vs on-premise auth differs: SaaS customers should use Epicor IdP or Azure AD for SSO with MFA. On-premise can use any method including Windows authentication for SOAP services. [src1]
Constraints
- REST API v2 mandatory API key -- All v2 requests require an API key header (
x-api-key) or query parameter (?api-key={value}), even when using bearer token authentication. - Functions require v2 -- Epicor Functions (EFx) are only accessible via REST API v2 endpoints at
/api/v2/efx/{Company}/{Library}/{Function}. V1 endpoints cannot invoke Functions. - SaaS restricts direct database access -- Cloud SaaS customers cannot run custom SQL or access the database directly. All custom data must go through UD tables, Cloud SDK, or Epicor Functions.
- BPM custom code limited in SaaS -- While C# custom code is allowed in BPM directives, SaaS environments restrict which .NET namespaces and assemblies are available. No arbitrary DLL loading.
- Kinetic UI consumes v1 internally -- The Kinetic browser UI uses REST API v1 for ~99% of operations. Blocking v1 externally is possible, but disabling it entirely breaks the UI.
- License seat consumption -- Each authenticated REST session (Basic or Bearer) consumes an Epicor license seat. Dedicated integration user accounts are recommended to avoid contention.
Integration Pattern Decision Tree
START -- Integrate with Epicor Kinetic
|-- What's the deployment model?
| |-- Cloud SaaS
| | |-- Authentication: Azure AD / Epicor IdP (recommended)
| | |-- Custom logic: Epicor Functions (EFx) via Cloud SDK
| | |-- Custom tables: UD Table Designer (no direct SQL)
| |-- On-Premise
| | |-- Authentication: Bearer token or Basic + API key
| | |-- Custom logic: Functions, BPM directives, or direct BO calls
| | |-- Custom tables: UD tables or direct SQL (if authorized)
| |-- Hybrid
| |-- Follow SaaS rules for Epicor-hosted components
| |-- Follow on-premise rules for local components
|-- What integration pattern?
| |-- Real-time CRUD (individual records)
| | |-- Use REST API v2 with Business Object endpoints
| | |-- Example: POST /api/v2/odata/{Company}/Erp.BO.SalesOrderSvc/SalesOrders
| |-- Event-driven (trigger on data change)
| | |-- Use BPM Data Directives to fire on insert/update/delete
| | |-- BPM calls Epicor Function which calls external REST endpoint
| |-- Custom server-side logic
| | |-- Create Epicor Function library
| | |-- Expose via REST: /api/v2/efx/{Company}/{Library}/{Function}
| | |-- Call from BPM directives, other Functions, or external REST
| |-- Batch/scheduled
| |-- Use BAQ REST endpoints for reads
| |-- Use BO endpoints with pagination for writes
| |-- Consider external orchestrator (Azure Logic Apps, n8n)
|-- Which API version?
| |-- v2 for all new external integrations (requires API key)
| |-- v1 only if legacy integration cannot add API key header
|-- Error handling?
|-- Implement token refresh before expiry
|-- Handle 401 (re-authenticate) and 429 (back off)
|-- Log all failures to external system (BPM error logging)
Quick Reference
| Operation | Method | Endpoint Pattern | Auth Required | Notes |
|---|---|---|---|---|
| Get bearer token | POST | /{instance}/TokenResource.svc | username/password headers | Returns JWT with configurable lifetime |
| List Business Objects | GET | /api/v2/odata/{Company}/$metadata | Bearer + API key | OData metadata document |
| Query records (BO) | GET | /api/v2/odata/{Company}/Erp.BO.{Service}/{Entity} | Bearer + API key | Supports $filter, $select, $top, $skip |
| Create record | POST | /api/v2/odata/{Company}/Erp.BO.{Service}/{Entity} | Bearer + API key | JSON body matching entity schema |
| Update record | PATCH | /api/v2/odata/{Company}/Erp.BO.{Service}/{Entity}('{key}') | Bearer + API key | Partial update supported |
| Execute BAQ | GET | /api/v2/odata/{Company}/BaqSvc/{BaqName}/Data | Bearer + API key | Read-only; parameterized BAQs supported |
| Call Function | POST | /api/v2/efx/{Company}/{Library}/{Function} | Bearer + API key | JSON body with function parameters |
| List Function libraries | GET | /api/v2/efx/{Company} | Bearer + API key | Returns all published libraries |
| Call BO method | POST | /api/v2/odata/{Company}/Erp.BO.{Service}/{Method} | Bearer + API key | For non-CRUD operations (e.g., GetNewSalesOrder) |
Step-by-Step Integration Guide
1. Create an API key in Epicor Admin Console
Navigate to Epicor Admin Console > REST Settings > API Keys. Create a new key with a descriptive name. Copy the key value -- it is shown only once. [src4]
Admin Console Steps:
1. Open Epicor Admin Console (https://{server}/{instance}/admin)
2. Navigate to REST Settings > API Keys
3. Click "Add" to create new API key
4. Enter description: "External Integration - {System Name}"
5. Copy the generated API key value immediately
6. Optionally restrict the key to specific users/roles
Verify: GET /api/v2/odata/{Company}/Erp.BO.CompanySvc/Companies with the API key header returns company list.
2. Obtain a bearer token from TokenResource.svc
POST to the TokenResource.svc endpoint with username and password headers. The response contains a JWT access token with configurable expiry (default 1 hour). [src2, src3]
curl -X POST "https://{server}/{instance}/TokenResource.svc" \
-H "username: epicor_integration_user" \
-H "password: YourSecurePassword" \
-H "Accept: application/json"
# Response:
# {
# "AccessToken": "eyJhbGciOiJIUzI1NiIs...",
# "ExpiresIn": 3600,
# "TokenType": "Bearer"
# }
Verify: Decode the JWT at jwt.io to confirm correct username and expiry claims.
3. Make an authenticated API call with bearer token + API key
Include both the Authorization header (Bearer token) and the API key header in every REST API v2 request. [src2, src4]
curl -X GET "https://{server}/{instance}/api/v2/odata/{Company}/Erp.BO.CustomerSvc/Customers?\$top=5&\$select=CustID,Name" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "x-api-key: your-api-key-here" \
-H "Accept: application/json"
Verify: Response returns HTTP 200 with JSON array of customer records. If 401: check both bearer token validity and API key.
4. Set up a BPM Method Directive for event-driven integration
Navigate to System Setup > Business Process Management > Method Directive Maintenance. Select the target BO and method, add a post-processing directive with custom C# code or Function call. [src5]
BPM Method Directive Setup:
1. Menu: System Setup > BPM > Method Directive Maintenance
2. Select BO: Erp.BO.SalesOrderSvc
3. Select Method: Update
4. Add Post-Processing Directive
5. Choose: "Execute Custom Code" or "Invoke Epicor Function"
6. Map input parameters from transaction context (ttSalesOrder)
7. Enable directive and save
Verify: Create or update a sales order. Check BPM logs for execution confirmation.
5. Create and call an Epicor Function via REST
Create a Function Library in Application Studio, add a function with inputs/outputs, write C# logic, publish, then call via REST v2 EFx endpoint. [src6, src7]
Function Creation Steps:
1. Open Epicor Application Studio (or Function Maintenance)
2. Create new Function Library: "IntegrationLib"
3. Add Function: "SyncCustomerToExternal"
4. Define inputs: custID (string), company (string)
5. Define outputs: success (bool), message (string)
6. Write C# logic in the code editor
7. Publish the library
8. Call via REST: POST /api/v2/efx/{Company}/IntegrationLib/SyncCustomerToExternal
Verify: GET /api/v2/efx/{Company} lists your library. POST to function endpoint with test parameters and confirm response.
Code Examples
Python: Obtain bearer token and query records
# Input: Epicor server URL, username, password, API key, company
# Output: List of sales orders from Epicor Kinetic
import requests
server = "https://your-epicor-server.com/EpicorInstance"
username = "integration_user"
password = "SecurePassword123"
api_key = "your-api-key-value"
company = "EPIC06"
# Step 1: Get bearer token
token_response = requests.post(
f"{server}/TokenResource.svc",
headers={
"username": username,
"password": password,
"Accept": "application/json"
}
)
token_data = token_response.json()
access_token = token_data["AccessToken"]
# Step 2: Query sales orders with bearer token + API key
headers = {
"Authorization": f"Bearer {access_token}",
"x-api-key": api_key,
"Accept": "application/json"
}
response = requests.get(
f"{server}/api/v2/odata/{company}/Erp.BO.SalesOrderSvc/SalesOrders",
headers=headers,
params={"$top": 10, "$select": "OrderNum,OrderDate,CustomerCustID"}
)
if response.status_code == 200:
orders = response.json().get("value", [])
for order in orders:
print(f"Order {order['OrderNum']}: {order['CustomerCustID']}")
else:
print(f"Error {response.status_code}: {response.text}")
JavaScript/Node.js: Call an Epicor Function via REST
// Input: Epicor server URL, credentials, API key, company, function params
// Output: Function execution result
// npm install node-fetch@2
const fetch = require("node-fetch");
const server = "https://your-epicor-server.com/EpicorInstance";
const company = "EPIC06";
const apiKey = process.env.EPICOR_API_KEY;
async function getToken() {
const resp = await fetch(`${server}/TokenResource.svc`, {
method: "POST",
headers: {
username: process.env.EPICOR_USER,
password: process.env.EPICOR_PASS,
Accept: "application/json",
},
});
const data = await resp.json();
return data.AccessToken;
}
async function callFunction(token, library, funcName, params) {
const resp = await fetch(
`${server}/api/v2/efx/${company}/${library}/${funcName}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"x-api-key": apiKey,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(params),
}
);
return resp.json();
}
(async () => {
const token = await getToken();
const result = await callFunction(token, "IntegrationLib", "SyncCustomer", {
custID: "CUST001",
company: "EPIC06",
});
console.log("Function result:", result);
})();
cURL: Quick authentication test and record query
# Input: server URL, credentials, API key, company
# Output: Bearer token + customer records
# Step 1: Get bearer token
TOKEN=$(curl -s -X POST \
"https://server/instance/TokenResource.svc" \
-H "username: epicor_user" \
-H "password: YourPassword" \
-H "Accept: application/json" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['AccessToken'])")
# Step 2: Query customers with token + API key
curl -s "https://server/instance/api/v2/odata/EPIC06/Erp.BO.CustomerSvc/Customers?\$top=5" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: your-api-key" \
-H "Accept: application/json" | python3 -m json.tool
# Step 3: Call an Epicor Function
curl -s -X POST \
"https://server/instance/api/v2/efx/EPIC06/MyLibrary/MyFunction" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"param1": "value1", "param2": "value2"}' | python3 -m json.tool
Data Mapping
Epicor REST API Endpoint Patterns
| Resource Type | v1 Endpoint | v2 Endpoint | Key Difference |
|---|---|---|---|
| Business Object CRUD | /api/v1/Erp.BO.{Svc}/{Entity} | /api/v2/odata/{Company}/Erp.BO.{Svc}/{Entity} | v2 requires Company in path + API key |
| BAQ Data | /api/v1/BaqSvc/{BaqName} | /api/v2/odata/{Company}/BaqSvc/{BaqName}/Data | v2 adds /Data suffix |
| Epicor Functions | N/A | /api/v2/efx/{Company}/{Library}/{Function} | Functions are v2 only |
| BO Methods | /api/v1/Erp.BO.{Svc}/{Method} | /api/v2/odata/{Company}/Erp.BO.{Svc}/{Method} | Same pattern, v2 adds Company |
| OData Metadata | /api/v1/$metadata | /api/v2/odata/{Company}/$metadata | v2 is Company-scoped |
Data Type Gotchas
- Date fields may truncate timezone: Some fields return
2026-03-02T00:00:00without timezone offset. Treat Epicor dates as server-local timezone unless explicitly specified. [src8] - Boolean fields use true/false, not 1/0: Epicor REST API expects JSON boolean
true/false, not integer equivalents. Sending1instead oftruemay cause 400 Bad Request. [src8] - Decimal precision varies by field: Currency fields typically use 2 decimal places, but quantity fields may use up to 8. Check OData
$metadatafor Precision and Scale attributes. [src8]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 401 Unauthorized | Authentication failed | Expired token, missing API key, invalid credentials | Check both bearer token validity AND API key header. Re-authenticate if token expired. |
| 403 Forbidden | Authorization failed | User lacks permission for the requested BO/method | Check Epicor security group and menu security for the integration user. |
| 404 Not Found | Endpoint or record not found | Wrong BO name, incorrect API version path, nonexistent record key | Verify endpoint against REST Help page. Check v1 vs v2 path format. |
| 400 Bad Request | Invalid payload | Malformed JSON, wrong field types, missing required fields | Check $metadata for field requirements. Validate JSON before sending. |
| 500 Internal Server Error | Server-side error | BPM custom code exception, database constraint violation | Check Epicor server event log. Review directive code for unhandled exceptions. |
| 503 Service Unavailable | Server overloaded or maintenance | Too many concurrent connections or scheduled maintenance | Implement retry with exponential backoff. Check server status. |
Failure Points in Production
- Bearer token expires during batch job: A long-running process uses the same token for hours, but the token expires mid-batch. Fix:
Check token expiry before each batch. Re-authenticate proactively when >80% of lifetime has elapsed.[src3] - API key rotated without updating integration: Admin rotates the API key, breaking all integrations using the old key. Fix:
Maintain API key in a configuration store (env var, vault). Implement key rotation process that updates all consumers.[src4] - BPM custom code throws unhandled exception: A method directive with C# code encounters a null reference, causing the entire BO transaction to fail. Fix:
Wrap all BPM custom code in try/catch. Log errors to a UD table or external service.[src5] - License seat exhaustion blocks API calls: Each REST session consumes a license seat. If all seats are in use, new connections are rejected. Fix:
Use a dedicated integration user. Implement connection pooling. Close sessions explicitly.[src8] - SaaS environment update breaks custom code: Epicor applies a SaaS update that changes available namespaces or BO behavior, breaking BPM code. Fix:
Test all customizations in pilot environment before Epicor applies update to production.[src7]
Anti-Patterns
Wrong: Sending credentials with every REST call (Basic auth in production)
# BAD -- credentials transmitted on every request, no caching
for order_num in order_numbers:
response = requests.get(
f"{server}/api/v2/odata/{company}/Erp.BO.SalesOrderSvc/SalesOrders('{order_num}')",
auth=("username", "password"), # credentials sent every time
headers={"x-api-key": api_key}
)
Correct: Obtain bearer token once, reuse until near expiry
# GOOD -- authenticate once, reuse token for all requests
import time
token_data = get_token(server, username, password)
token = token_data["AccessToken"]
token_expiry = time.time() + token_data["ExpiresIn"] - 300 # 5 min buffer
for order_num in order_numbers:
if time.time() > token_expiry:
token_data = get_token(server, username, password)
token = token_data["AccessToken"]
token_expiry = time.time() + token_data["ExpiresIn"] - 300
response = requests.get(
f"{server}/api/v2/odata/{company}/Erp.BO.SalesOrderSvc/SalesOrders('{order_num}')",
headers={"Authorization": f"Bearer {token}", "x-api-key": api_key}
)
Wrong: Putting business logic directly in BPM directives
// BAD -- complex integration logic embedded in BPM directive
// Hard to test, hard to reuse, hard to debug
var client = new System.Net.Http.HttpClient();
var json = Newtonsoft.Json.JsonConvert.SerializeObject(ttSalesOrder);
var response = client.PostAsync("https://external-system.com/api",
new StringContent(json, Encoding.UTF8, "application/json")).Result;
// No error handling, blocks the transaction, not reusable
Correct: Call an Epicor Function from BPM, keep logic modular
// GOOD -- BPM directive calls a Function; logic is in the Function
// Function can be tested independently, reused, called via REST
CallService<Ice.Contracts.EfxSvcContract>(efxSvc =>
{
var request = new EfxCallRequest("IntegrationLib", "SyncOrderToExternal");
request.Parameters.Add("orderNum", ttSalesOrder.FirstOrDefault()?.OrderNum);
var result = efxSvc.Execute(request);
});
Wrong: Using REST API v1 for new external integrations
# BAD -- v1 lacks API key security, no access to Functions
curl "https://server/instance/api/v1/Erp.BO.CustomerSvc/Customers" \
-H "Authorization: Basic dXNlcjpwYXNz"
# No API key = no application-level access control
Correct: Use REST API v2 with API key for all new integrations
# GOOD -- v2 with API key provides application-level security
curl "https://server/instance/api/v2/odata/EPIC06/Erp.BO.CustomerSvc/Customers" \
-H "Authorization: Bearer eyJhbGci..." \
-H "x-api-key: your-api-key-here"
# API key can be scoped, rotated, and revoked independently
Common Pitfalls
- Forgetting $Company in v2 paths: V2 endpoints require the company code in the URL path. Migrating from v1 to v2 without adding the company segment returns 404. Fix:
Always include company code in v2 paths. Check REST Help for exact format.[src4] - Not URL-encoding OData filter values: Special characters in $filter values must be URL-encoded. Fix:
Use OData-aware libraries or manually escape special characters.[src8] - Testing with admin account, deploying with restricted user: Everything works in dev because test account has full admin rights. Fix:
Create and test with the actual integration user account.[src8] - Ignoring BPM execution order: Multiple BPM directives on the same method execute by sequence number. Fix:
Document all BPM directives per method. Use sequence numbers deliberately.[src5] - Not handling partial failures in multi-record operations: A failure on record 5 of 10 may leave records 1-4 committed. Fix:
Implement idempotent operations. Track progress externally.[src8] - Hardcoding server URLs in BPM custom code: BPM code with hardcoded URLs breaks when migrating between environments. Fix:
Store configuration in Epicor User Codes. Read dynamically in Function code.[src5]
Diagnostic Commands
# Get bearer token and verify it works
TOKEN=$(curl -s -X POST "https://server/instance/TokenResource.svc" \
-H "username: your_user" -H "password: your_pass" \
-H "Accept: application/json" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['AccessToken'])")
# Decode JWT to check claims and expiry
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool
# Test authentication + API key with simple endpoint
curl -s "https://server/instance/api/v2/odata/EPIC06/Erp.BO.CompanySvc/Companies" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: your-api-key" \
-H "Accept: application/json" | python3 -m json.tool
# List available Epicor Function libraries
curl -s "https://server/instance/api/v2/efx/EPIC06" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: your-api-key" \
-H "Accept: application/json" | python3 -m json.tool
# Check OData metadata for a specific Business Object
curl -s "https://server/instance/api/v2/odata/EPIC06/Erp.BO.SalesOrderSvc/\$metadata" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: your-api-key" \
-H "Accept: application/xml"
# Test a BAQ endpoint
curl -s "https://server/instance/api/v2/odata/EPIC06/BaqSvc/zCustomerList/Data?\$top=5" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: your-api-key" \
-H "Accept: application/json" | python3 -m json.tool
Version History & Compatibility
| API Version | Release | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| REST v2 | 2022.2 | Current (recommended) | Mandatory API key; Company in URL path | Add API key header; add Company to URL paths |
| REST v1 | 10.0 (2014) | Supported (UI dependency) | None | Cannot access Functions; no API key security; still used by Kinetic UI |
| SOAP/WCF | 10.0 (2014) | Maintenance only | Deprecated for new development | Migrate to REST v2; SOAP may be removed in future |
| Epicor Functions (EFx) | 2021.1 | GA | N/A (new feature) | Requires v2 endpoints; replaces many BPM custom code patterns |
| Cloud SDK (UD Services) | 2023.x | GA | N/A (new feature) | SaaS-only; generates REST endpoints, tables, and UI automatically |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building external integrations with Epicor Kinetic (orders, inventory, customers) | Integrating with Epicor Prophet 21 (P21) | P21 has its own REST API with different auth |
| Need event-driven triggers on Epicor data changes (BPM directives) | Need real-time streaming/CDC from Epicor | Consider database-level CDC if on-premise; no native streaming API |
| Creating reusable server-side business logic (Epicor Functions) | Need sub-second latency for high-frequency systems | Epicor REST is not designed for ultra-low-latency use cases |
| SaaS deployment needing custom tables and logic (Cloud SDK) | Need direct SQL access to Epicor database | Only possible on-premise; SaaS prohibits direct DB access |
| Bearer token auth for server-to-server integration | Need interactive SSO with MFA for end users | Use Azure AD / Epicor IdP for interactive auth flows |
Important Caveats
- Epicor does not publish official rate limits for REST API calls. SaaS environments may throttle excessive usage, but the thresholds are not documented. Test your expected load against a non-production environment.
- The REST Help/Swagger UI (
/apps/resthelp/) is a valuable discovery tool but only supports Basic authentication. You cannot test bearer tokens or API keys there. - SaaS update schedules are controlled by Epicor, not the customer. Customizations (BPM code, Functions) that rely on undocumented behavior may break after an update.
- License seat consumption by REST sessions is a real cost concern. Each concurrent session typically consumes one seat. Plan integration architecture to minimize concurrent connections.
- Epicor Functions documentation is primarily available through in-product help and EpicWeb (partner portal). Public documentation is limited compared to vendors like Salesforce or Microsoft. Community resources (epiusers.help) are often the best source of real-world implementation patterns.