---
# === IDENTITY ===
id: business/erp-integration/epicor-authentication-customization/2026
canonical_question: "How do Epicor authentication, functions, and BPM customization APIs work?"
aliases:
  - "Epicor Kinetic REST API bearer token authentication setup"
  - "Epicor BPM method directives and data directives for integrations"
  - "Epicor Functions REST endpoint custom server-side logic"
  - "Epicor Kinetic API v1 vs v2 authentication and API key configuration"
entity_type: erp_integration
domain: business > erp-integration > epicor-authentication-customization
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === SYSTEM PROFILE ===
systems:
  - name: "Epicor Kinetic"
    vendor: "Epicor"
    version: "Kinetic 2024.x / 2025.x (REST API v2)"
    edition: "All editions (Cloud SaaS, On-Premise, Hybrid)"
    deployment: "hybrid"
    api_surface: "REST (OData v4)"

# === VERIFICATION ===
last_verified: 2026-03-02
confidence: 0.88
version: 1.0
first_published: 2026-03-02

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "2022.2 - REST API v2 introduced with mandatory API keys"
  next_review: 2026-08-29
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "TokenResource.svc bearer tokens default to 1 hour expiry; no built-in refresh token mechanism"
  - "REST API v2 requires an API key header or query parameter for all requests"
  - "Epicor Functions require REST API v2 - cannot be called via v1 endpoints"
  - "BPM custom code runs server-side in C#; no arbitrary .NET library imports allowed in SaaS"
  - "Swagger/REST Help UI only works with Basic authentication - cannot test bearer tokens there"
  - "SaaS deployments restrict direct database access - must use Cloud SDK and Functions"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs Epicor Prophet 21 (P21) API integration"
    use_instead: "N/A - P21 uses a different API architecture"
  - condition: "User needs Epicor for Retail or Epicor BisTrack integration"
    use_instead: "N/A - different product lines with separate API surfaces"
  - condition: "User needs general ERP authentication comparison across vendors"
    use_instead: "business/erp-integration/erp-authentication-comparison/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: deployment_model
    question: "What is your Epicor deployment model?"
    type: choice
    options:
      - "Cloud SaaS (Epicor-hosted)"
      - "On-premise (self-hosted)"
      - "Hybrid (Epicor-managed with local resources)"
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "real-time sync (individual records, <1s latency)"
      - "batch/bulk (scheduled, high volume)"
      - "event-driven (BPM triggers on data changes)"
      - "custom server-side logic (Epicor Functions)"
  - key: auth_method
    question: "Which authentication method do you prefer?"
    type: choice
    options:
      - "Bearer token (TokenResource.svc)"
      - "Basic authentication (username/password per request)"
      - "Azure AD / Epicor IdP (SSO with MFA)"
      - "API key only (simple external access)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/epicor-authentication-customization/2026"
suggested_citation: "Source: knowledgelib.io - AI Knowledge Library (verified 2026-03-02)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/epicor-rest-api/2026"
      label: "Epicor Kinetic REST API capabilities and rate limits"
  solves:
    - id: "business/erp-integration/salesforce-epicor-integration/2026"
      label: "Salesforce to Epicor integration playbook"
  alternative_to:
    - id: "business/erp-integration/dynamics-365-authentication/2026"
      label: "Dynamics 365 authentication (Microsoft Entra ID OAuth 2.0)"
  often_confused_with:
    - id: "business/erp-integration/epicor-prophet21-api/2026"
      label: "Epicor Prophet 21 API (different product line, different API surface)"

# === SOURCES ===
sources:
  - id: src1
    title: "REST API Authentication Options - Epicor ERP 10"
    author: Epicor Community
    url: https://www.epiusers.help/t/rest-api-authentication-options/69015
    type: community_resource
    published: 2024-03-15
    reliability: moderate_high
  - id: src2
    title: "REST with Bearer Tokens - ClientId and Client Secret"
    author: Epicor Community
    url: https://www.epiusers.help/t/rest-with-bearer-tokens-clientid-and-client-secret-s/126627
    type: community_resource
    published: 2025-06-01
    reliability: moderate_high
  - id: src3
    title: "REST API Bearer Token"
    author: Epicor Community
    url: https://www.epiusers.help/t/rest-api-bearer-token/122686
    type: community_resource
    published: 2025-03-10
    reliability: moderate_high
  - id: src4
    title: "Difference between API v1 and v2"
    author: Epicor Community
    url: https://www.epiusers.help/t/difference-between-api-v1-and-v2/104962
    type: community_resource
    published: 2024-06-15
    reliability: moderate_high
  - id: src5
    title: "Using Epicor BPMs to Trigger External Integrations"
    author: Epicforce Tech
    url: https://epicforcetech.com/using-epicor-bpms-to-trigger-external-integrations-without-full-customization/
    type: technical_blog
    published: 2025-01-20
    reliability: moderate_high
  - id: src6
    title: "Epicor Functions REST Endpoint for Listing Libraries"
    author: Epicor Community
    url: https://www.epiusers.help/t/is-there-a-rest-endpoint-for-listing-epicor-functions/82459
    type: community_resource
    published: 2024-04-20
    reliability: moderate_high
  - id: src7
    title: "Epicor Cloud SDK"
    author: Epicor
    url: https://www.epicor.com/en-us/products/enterprise-resource-planning-erp/kinetic/cloud-business-platform/ptw-cloud-sdk/
    type: official_docs
    published: 2025-01-01
    reliability: authoritative
  - id: src8
    title: "DIY Developer Guide: Building Custom Integrations for Epicor"
    author: ApiWorx
    url: https://apiworx.com/diy-developer-guide-building-custom-integrations-for-epicor/
    type: technical_blog
    published: 2024-09-15
    reliability: moderate
---

# Epicor Kinetic Authentication, Functions, and BPM Customization APIs

## 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. [src1, src2]
- **Key limit**: Bearer tokens default to 1 hour expiry with no refresh token mechanism; you must request a new token before expiry. Token lifetime is configurable via Admin Console. [src2, src3]
- **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. [src4]
- **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. [src1, src3]

## 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. [src7, src8]

| 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 URL** | [REST Help (instance-specific)](https://{server}/{instance}/apps/resthelp/) |
| **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. [src4]

| 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 |

[src1, src4, src8]

## 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 |

[src8]

### 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 |

[src1, src3]

## Authentication

Epicor Kinetic supports three primary authentication flows, with an API key required for all REST API v2 calls regardless of the auth method chosen. [src1, src2, src3]

| 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-encoded). Only method that works with 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] |

[src1, src2, src3]

### 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. You must 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. Always check your environment's setting. [src2, 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]
- **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]

## Constraints

- **REST API v2 mandatory API key** - All v2 requests require an API key header (`api-key`) or query parameter (`?api-key={value}`), even when using bearer token authentication. Forgetting this is the #1 integration failure. [src4]
- **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. [src4, src6]
- **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. [src7]
- **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. [src5]
- **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. [src4]
- **License seat consumption** - Each authenticated REST session (Basic or Bearer) consumes an Epicor license seat. Dedicated integration user accounts are recommended to avoid contention with interactive users. [src8]

## Integration Pattern Decision Tree

```
START - Integrate with Epicor Kinetic
|-- What's the deployment model?
|   |-- Cloud SaaS
|   |   |-- Authentication: Azure AD / Epicor IdP (recommended) [src1]
|   |   |-- Custom logic: Epicor Functions (EFx) via Cloud SDK [src7]
|   |   |-- Custom tables: UD Table Designer (no direct SQL) [src7]
|   |-- On-Premise
|   |   |-- Authentication: Bearer token or Basic + API key [src2]
|   |   |-- 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 [src8]
|   |   |-- 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 [src5]
|   |   |-- BPM calls Epicor Function which calls external REST endpoint
|   |-- Custom server-side logic
|   |   |-- Create Epicor Function library [src6]
|   |   |-- 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 [src8]
|   |   |-- 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) [src4]
|   |-- v1 only if legacy integration cannot add API key header
|-- Error handling?
    |-- Implement token refresh before expiry [src3]
    |-- Handle 401 (re-authenticate) and 429 (back off) [src8]
    |-- Log all failures to external system (BPM error logging) [src5]
```

## Quick Reference

| Operation | Method | Endpoint Pattern | Auth Required | Notes |
|---|---|---|---|---|
| Get bearer token | POST | `/{instance}/TokenResource.svc` | username/password headers | Returns JWT with configurable lifetime [src2] |
| List Business Objects | GET | `/api/v2/odata/{Company}/$metadata` | Bearer + API key | OData metadata document [src4] |
| Query records (BO) | GET | `/api/v2/odata/{Company}/Erp.BO.{Service}/{Entity}` | Bearer + API key | Supports `$filter`, `$select`, `$top`, `$skip` [src8] |
| Create record | POST | `/api/v2/odata/{Company}/Erp.BO.{Service}/{Entity}` | Bearer + API key | JSON body matching entity schema [src8] |
| Update record | PATCH | `/api/v2/odata/{Company}/Erp.BO.{Service}/{Entity}('{key}')` | Bearer + API key | Partial update supported [src8] |
| Execute BAQ | GET | `/api/v2/odata/{Company}/BaqSvc/{BaqName}/Data` | Bearer + API key | Read-only; parameterized BAQs supported [src8] |
| Call Function | POST | `/api/v2/efx/{Company}/{Library}/{Function}` | Bearer + API key | JSON body with function parameters [src6] |
| List Function libraries | GET | `/api/v2/efx/{Company}` | Bearer + API key | Returns all published libraries [src6] |
| Call BO method | POST | `/api/v2/odata/{Company}/Erp.BO.{Service}/{Method}` | Bearer + API key | For non-CRUD operations (e.g., GetNewSalesOrder) [src8] |

## 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 (e.g., "Integration-Production"). Copy the key value - it is shown only once. Assign the key to specific users or security groups to control scope. [src4]

```plaintext
Admin Console Steps:
1. Open Epicor Admin Console (https://{server}/{instance}/admin)
2. Navigate to REST Settings (or Server Management > REST)
3. Click "API Keys" section
4. Click "Add" to create new API key
5. Enter description: "External Integration - {System Name}"
6. Copy the generated API key value immediately
7. Optionally restrict the key to specific users/roles
```

**Verify**: Make a test call: `GET /api/v2/odata/{Company}/Erp.BO.CompanySvc/Companies` with the API key header. Should return 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 a configurable expiry (default 1 hour). [src2, src3]

```bash
# Input:  Epicor server URL, username, password
# Output: JWT bearer token

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 the token contains the 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]

```bash
# Input:  bearer token, API key, company ID
# Output: list of customers

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

In Epicor Kinetic, navigate to System Setup > Business Process Management > Method Directive Maintenance. Select the target Business Object and method (e.g., `SalesOrderSvc.Update` for sales order changes). Add a post-processing directive with custom C# code or a Function call. [src5]

```plaintext
BPM Method Directive Setup:
1. Menu: System Setup > Business Process Management > Method Directive Maintenance
2. Select BO: Erp.BO.SalesOrderSvc
3. Select Method: Update (fires on every sales order save)
4. Add Post-Processing Directive
5. Choose: "Execute Custom Code" or "Invoke Epicor Function"
6. For Function call: select Library and Function name
7. Map input parameters from the transaction context (ttSalesOrder)
8. Enable directive and save
```

**Verify**: Create or update a sales order. The BPM directive should fire. Check BPM logs for execution confirmation.

### 5. Create and call an Epicor Function via REST

Create a Function Library in Epicor Application Studio, add a function with inputs/outputs, write C# logic, publish the library, then call it via the REST v2 EFx endpoint. [src6, src7]

```plaintext
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}` should list your library. POST to the function endpoint with test parameters and confirm the response.

## Code Examples

### Python: Obtain bearer token and query records

```python
# 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

```javascript
// 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

```bash
# 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 [src4] |
| BAQ Data | `/api/v1/BaqSvc/{BaqName}` | `/api/v2/odata/{Company}/BaqSvc/{BaqName}/Data` | v2 adds `/Data` suffix [src4] |
| Epicor Functions | N/A | `/api/v2/efx/{Company}/{Library}/{Function}` | Functions are v2 only [src6] |
| BO Methods | `/api/v1/Erp.BO.{Svc}/{Method}` | `/api/v2/odata/{Company}/Erp.BO.{Svc}/{Method}` | Same pattern, v2 adds Company [src4] |
| OData Metadata | `/api/v1/$metadata` | `/api/v2/odata/{Company}/$metadata` | v2 is Company-scoped [src4] |
| REST Help/Swagger | `/apps/resthelp/` | `/apps/resthelp/` | Shared; only supports Basic auth [src1] |

### Data Type Gotchas

- **Date fields return ISO 8601 but Epicor may truncate time zone**: Some date fields return `2026-03-02T00:00:00` without a timezone offset. Always treat Epicor dates as the server's 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. Sending `1` instead of `true` may cause a 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 the OData `$metadata` for `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. [src2, src4] |
| 403 Forbidden | Authorization failed | User lacks permission for the requested BO/method | Check Epicor security group and menu security for the integration user. [src8] |
| 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. [src4] |
| 400 Bad Request | Invalid payload | Malformed JSON, wrong field types, missing required fields | Check `$metadata` for field requirements. Validate JSON before sending. [src8] |
| 500 Internal Server Error | Server-side error | BPM custom code exception, database constraint violation | Check Epicor server event log. If BPM-related, review directive code for unhandled exceptions. [src5] |
| 503 Service Unavailable | Server overloaded or maintenance | Too many concurrent connections or scheduled maintenance window | Implement retry with exponential backoff. Check server status. [src8] |

[src1, src4, src8]

### Failure Points in Production

- **Bearer token expires during batch job**: A long-running integration process uses the same token for hours, but the token expires mid-batch. Fix: `Check token expiry before each batch of requests. Re-authenticate proactively when >80% of token lifetime has elapsed.` [src3]
- **API key rotated without updating integration**: Admin rotates the API key for security, breaking all integrations using the old key. Fix: `Maintain API key in a configuration store (environment variable, vault). Implement a key rotation process that updates all consuming systems simultaneously.` [src4]
- **BPM custom code throws unhandled exception**: A method directive with custom 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. Never let custom code crash the parent transaction silently.` [src5]
- **License seat exhaustion blocks API calls**: Each REST session consumes a license seat. If all seats are in use, new REST connections are rejected. Fix: `Use a dedicated integration user account. Implement connection pooling. Close sessions explicitly when done.` [src8]
- **SaaS environment update breaks custom code**: Epicor applies a SaaS update that changes available namespaces or BO behavior, breaking BPM custom code. Fix: `Test all customizations in a pilot environment before Epicor applies the update to production. Subscribe to Epicor release notes.` [src7]

## Anti-Patterns

### Wrong: Sending credentials with every REST call (Basic auth in production)

```python
# 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

```python
# GOOD - authenticate once, reuse token for all requests
import time

token_data = get_token(server, username, password)  # single auth call
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)  # refresh
        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

```csharp
// BAD - complex integration logic embedded in BPM directive
// Hard to test, hard to reuse, hard to debug
// If this fails, the entire BO transaction fails
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

```csharp
// GOOD - BPM directive calls a Function; logic is in the Function
// Function can be tested independently, reused across BPMs, 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);
    // Handle result in BPM context
});
```

### Wrong: Using REST API v1 for new external integrations

```bash
# 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 required = no application-level access control
```

### Correct: Use REST API v2 with API key for all new integrations

```bash
# 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 (`/api/v2/odata/{Company}/...`). V1 does not. Migrating from v1 to v2 without adding the company segment returns 404. Fix: `Always include the company code in v2 paths. Check your REST Help page for the exact format.` [src4]
- **Not URL-encoding OData filter values**: Special characters in `$filter` values (spaces, apostrophes, ampersands) must be URL-encoded. `$filter=Name eq 'O'Brien'` breaks; use `$filter=Name eq 'O''Brien'` (double apostrophe for OData escaping). Fix: `Use OData-aware libraries that handle escaping, or manually escape special characters.` [src8]
- **Testing with admin account, deploying with restricted user**: Everything works in dev because the test account has full admin rights. Production integration user lacks specific BO or method permissions. Fix: `Create and test with the actual integration user account. Use Security Maintenance to verify BO-level and method-level access.` [src8]
- **Ignoring BPM execution order**: Multiple BPM directives on the same method execute in order of their sequence number. A pre-processing directive that modifies data may conflict with another. Fix: `Document all BPM directives per method. Use sequence numbers deliberately. Test directive interactions.` [src5]
- **Not handling partial failures in multi-record operations**: Epicor processes records sequentially in most BO operations. A failure on record 5 of 10 may leave records 1-4 committed. Fix: `Implement idempotent operations. Track progress externally. Design for replay from any point of failure.` [src8]
- **Hardcoding server URLs in BPM custom code**: BPM code with hardcoded URLs breaks when migrating between environments (dev/test/prod). Fix: `Store configuration in Epicor User Codes (Ice.BO.UserCodesSvc). Read configuration dynamically in Function code.` [src5]

## Diagnostic Commands

```bash
# 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 to all requests; add Company to URL paths [src4] |
| REST v1 | 10.0 (2014) | Supported (UI dependency) | None | Cannot access Functions; no API key security; still used by Kinetic UI [src4] |
| SOAP/WCF | 10.0 (2014) | Maintenance only | Deprecated for new development | Migrate to REST v2; SOAP may be removed in future releases [src8] |
| Epicor Functions (EFx) | 2021.1 | GA | N/A (new feature) | Requires v2 endpoints; replaces many BPM custom code patterns [src6, src7] |
| Cloud SDK (UD Services) | 2023.x | GA | N/A (new feature) | SaaS-only; generates REST endpoints, tables, and UI automatically [src7] |

[src4, src6, src7]

### Deprecation Policy

Epicor does not publish a formal API deprecation timeline, but REST v1 is effectively in maintenance mode with no new features. SaaS customers receive automatic updates on Epicor's schedule (typically quarterly), which may change API behavior. On-premise customers control their upgrade timing. Always test customizations in a pilot/sandbox environment before applying Epicor updates. [src4, src7]

## 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 trading 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 [src7] |
| 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 [src1] |

## 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. [src8]
- 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. Use Postman or similar tools for realistic testing. [src1]
- SaaS update schedules are controlled by Epicor, not the customer. Customizations (BPM code, Functions) that rely on undocumented behavior may break after an update. Always test in pilot environments. [src7]
- License seat consumption by REST sessions is a real cost concern. Each concurrent session (whether Basic or Bearer) typically consumes one seat. Plan integration architecture to minimize concurrent connections. [src8]
- Epicor Functions documentation is primarily available through the 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 most practical source of real-world implementation patterns. [src6]

## Related Units

- [Epicor Kinetic REST API capabilities and rate limits](/business/erp-integration/epicor-rest-api/2026)
- [Dynamics 365 authentication (Microsoft Entra ID OAuth 2.0)](/business/erp-integration/dynamics-365-authentication/2026)
- [Salesforce to Epicor integration playbook](/business/erp-integration/salesforce-epicor-integration/2026)
