Epicor Kinetic Authentication, Functions, and BPM Customization APIs

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

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.

PropertyValue
VendorEpicor
SystemEpicor Kinetic (ERP 10+)
API SurfaceREST (OData v4), SOAP (legacy)
Current API Versionv2 (introduced 2022.2; v1 still active for Kinetic UI)
Editions CoveredCloud SaaS, On-Premise, Hybrid
DeploymentHybrid (cloud + on-premise supported)
API DocsEpicor Cloud SDK & REST Help (instance-specific)
StatusGA

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 SurfaceProtocolBest ForAuth MethodsReal-time?Bulk?
REST API v2HTTPS/JSON (OData)External integrations, FunctionsBearer token, Basic + API keyYesLimited
REST API v1HTTPS/JSON (OData)Kinetic UI, legacy integrationsBearer token, BasicYesLimited
Epicor Functions (EFx)HTTPS/JSON via v2Custom server-side logic, reusable business rulesBearer token + API keyYesNo
BAQ REST endpointsHTTPS/JSON (OData)Custom queries, reporting, read-heavy workloadsBearer token, Basic + API keyYesNo
SOAP/WCF ServicesHTTPS/XMLLegacy integrations onlyBasic, Windows authYesNo
OData FeedsOData v4Excel, Power BI, BI toolsBasic + API keyYesNo

Rate Limits & Quotas

Per-Request Limits

Limit TypeValueApplies ToNotes
Max records per OData query100 default ($top)All REST endpointsUse $skip for pagination; configurable via server settings
Max request body sizeVaries by server configPOST/PATCH operationsDefault IIS limit applies (~28.6 MB for on-premise)
Max concurrent sessionsServer-dependentAll authenticated usersControlled by Epicor license count
BAQ query timeout30 seconds defaultBAQ REST endpointsConfigurable in BAQ designer

Rolling / Daily Limits

Limit TypeValueWindowEdition Differences
API callsNo hard published limitN/ASaaS: Epicor may throttle excessive usage; On-premise: limited by server capacity
Concurrent REST connectionsLicense-dependentPer-serverEach connection consumes a license seat unless using a dedicated integration account
TokenResource.svc requestsNo explicit limitN/ARate-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.

FlowUse WhenToken LifetimeRefresh?Notes
Basic AuthenticationQuick testing, simple scripts, Swagger UIN/A (per-request)N/ACredentials sent with every request (Base64). Only method for REST Help/Swagger UI. [src1]
Bearer Token (TokenResource.svc)Server-to-server integrations, scheduled jobsDefault 1h (configurable up to 100h+)No refresh token; request new token before expiryPOST to TokenResource.svc with username/password headers. Returns JWT. [src2, src3]
Azure AD / Epicor IdPSaaS 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 callsDoes not expire (until rotated)N/ARequired in addition to any auth method above. Passed as header or query parameter. [src4]

Authentication Gotchas

Constraints

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

OperationMethodEndpoint PatternAuth RequiredNotes
Get bearer tokenPOST/{instance}/TokenResource.svcusername/password headersReturns JWT with configurable lifetime
List Business ObjectsGET/api/v2/odata/{Company}/$metadataBearer + API keyOData metadata document
Query records (BO)GET/api/v2/odata/{Company}/Erp.BO.{Service}/{Entity}Bearer + API keySupports $filter, $select, $top, $skip
Create recordPOST/api/v2/odata/{Company}/Erp.BO.{Service}/{Entity}Bearer + API keyJSON body matching entity schema
Update recordPATCH/api/v2/odata/{Company}/Erp.BO.{Service}/{Entity}('{key}')Bearer + API keyPartial update supported
Execute BAQGET/api/v2/odata/{Company}/BaqSvc/{BaqName}/DataBearer + API keyRead-only; parameterized BAQs supported
Call FunctionPOST/api/v2/efx/{Company}/{Library}/{Function}Bearer + API keyJSON body with function parameters
List Function librariesGET/api/v2/efx/{Company}Bearer + API keyReturns all published libraries
Call BO methodPOST/api/v2/odata/{Company}/Erp.BO.{Service}/{Method}Bearer + API keyFor 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 Typev1 Endpointv2 EndpointKey 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}/Datav2 adds /Data suffix
Epicor FunctionsN/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}/$metadatav2 is Company-scoped

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

CodeMeaningCauseResolution
401 UnauthorizedAuthentication failedExpired token, missing API key, invalid credentialsCheck both bearer token validity AND API key header. Re-authenticate if token expired.
403 ForbiddenAuthorization failedUser lacks permission for the requested BO/methodCheck Epicor security group and menu security for the integration user.
404 Not FoundEndpoint or record not foundWrong BO name, incorrect API version path, nonexistent record keyVerify endpoint against REST Help page. Check v1 vs v2 path format.
400 Bad RequestInvalid payloadMalformed JSON, wrong field types, missing required fieldsCheck $metadata for field requirements. Validate JSON before sending.
500 Internal Server ErrorServer-side errorBPM custom code exception, database constraint violationCheck Epicor server event log. Review directive code for unhandled exceptions.
503 Service UnavailableServer overloaded or maintenanceToo many concurrent connections or scheduled maintenanceImplement retry with exponential backoff. Check server status.

Failure Points in Production

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

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 VersionReleaseStatusBreaking ChangesMigration Notes
REST v22022.2Current (recommended)Mandatory API key; Company in URL pathAdd API key header; add Company to URL paths
REST v110.0 (2014)Supported (UI dependency)NoneCannot access Functions; no API key security; still used by Kinetic UI
SOAP/WCF10.0 (2014)Maintenance onlyDeprecated for new developmentMigrate to REST v2; SOAP may be removed in future
Epicor Functions (EFx)2021.1GAN/A (new feature)Requires v2 endpoints; replaces many BPM custom code patterns
Cloud SDK (UD Services)2023.xGAN/A (new feature)SaaS-only; generates REST endpoints, tables, and UI automatically

When to Use / When Not to Use

Use WhenDon't Use WhenUse 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 EpicorConsider 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 systemsEpicor 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 databaseOnly possible on-premise; SaaS prohibits direct DB access
Bearer token auth for server-to-server integrationNeed interactive SSO with MFA for end usersUse Azure AD / Epicor IdP for interactive auth flows

Important Caveats

Related Units