Calling the Kinetic REST v2 API
In short. REST v2 URLs name the company and the service, every call carries an API key, child collections need $expand and large reads must page. Most integration surprises come from those four facts.
Written for Developers.
Kinetic exposes its business objects, BAQs and Epicor Functions over REST. We recommend version 2 of that API for new work. It follows OData conventions closely, which is convenient once you know them and confusing when an OData behavior looks like a bug. Below is what we explain to every developer on their first Kinetic integration.
The URL shape
Section titled: The URL shapeBusiness object endpoints follow this pattern:
https://{server}/{instance}/api/v2/odata/{Company}/Erp.BO.{Service}Svc/{EntitySet}| Segment | Meaning |
|---|---|
{server}/{instance} |
Your Kinetic application server and instance |
api/v2/odata |
Selects REST version 2 |
{Company} |
The company ID. It is part of every v2 business object URL, so a call is always scoped to a company |
Erp.BO.{Service}Svc |
The business object service, for example Erp.BO.SalesOrderSvc or Erp.BO.CustomerSvc |
{EntitySet} |
The collection inside the service, for example SalesOrders or Customers |
So a read of sales orders for company ACME looks like:
GET https://erp.example.com/KineticLive/api/v2/odata/ACME/Erp.BO.SalesOrderSvc/SalesOrders?$top=10Business object methods are called by name with a POST to the service, with the method's parameters in a JSON body:
POST https://erp.example.com/KineticLive/api/v2/odata/ACME/Erp.BO.SalesOrderSvc/{MethodName}Appending $metadata to a service URL returns the OData description of that service: its entity sets, fields, keys and navigation properties. When in doubt about a name, read the metadata rather than guessing.
Authentication and API keys
Section titled: Authentication and API keysA v2 call carries two kinds of credentials, shown in the table.
| Credential | Sent as | What it decides |
|---|---|---|
| User authentication | Basic authentication with a Kinetic user, or a bearer token from an identity provider, depending on configuration | Whose permissions the call runs with |
| API key | The x-api-key header |
Which calling application this is. Keys are created in Kinetic and can be tied to an access scope that limits which services, BAQs and functions the key can reach |
A minimal request with curl:
curl -s \ -H "Authorization: Bearer $KINETIC_TOKEN" \ -H "x-api-key: $KINETIC_API_KEY" \ -H "Accept: application/json" \ "https://erp.example.com/KineticLive/api/v2/odata/ACME/Erp.BO.CustomerSvc/Customers?\$select=CustID,Name&\$top=5"Adopt these practices early:
- Use one API key per integration, each with the narrowest access scope that works. A leaked key then exposes one integration, not the whole system.
- Run calls as a dedicated integration user rather than a personal account, so a departure or password change does not break production.
- Keep secrets in a secrets manager, never in source code or in a shared script.
- Plan rotation. Know how you will replace a key without downtime before you need to.
Reading data with $filter, $select and $orderby
Section titled: Reading data with $filter, $select and $orderbyThe standard OData query options work as you would expect:
| Option | Purpose | Example |
|---|---|---|
$filter |
Restrict rows | $filter=OrderDate ge 2026-01-01T00:00:00Z |
$select |
Return only named fields | $select=OrderNum,CustNum,OrderDate |
$orderby |
Sort | $orderby=OrderNum desc |
$top |
Limit rows | $top=100 |
$skip |
Skip rows, for paging | $skip=200 |
$expand |
Include related child records | $expand=OrderDtls |
String values in $filter are quoted with single quotes, and a single quote inside a value is escaped by doubling it. URL-encode the whole query string.
Empty child collections and $expand
Section titled: Empty child collections and $expandThis is the behavior that surprises almost everyone.
A business object entity has navigation properties for its child records: a sales order has lines, a line has releases. As with other OData services, child collections are not populated unless you ask for them with $expand.
They can come back as empty collections when you query the top-level entity set, for example with a $filter. The order comes back, and it looks as if it has no lines. Nothing is wrong with the order. OData does not include related records unless you request them:
GET .../Erp.BO.SalesOrderSvc/SalesOrders?$filter=OrderNum eq 5001&$expand=OrderDtls&$select=OrderNum,CustNumA few more points apply to expansion:
- Use the navigation property name from
$metadata. It is similar to the table name you know from BAQs, but not always identical. - You can shape the expansion. OData allows options inside an expand, such as selecting fields of the child:
$expand=OrderDtls($select=OrderLine,PartNum,OrderQty). - Expansion multiplies payload size. Expanding lines and releases for a thousand orders can produce a large response. Page the parent and select narrowly.
- If you only need child rows, query them directly. Many services expose child entity sets, or a BAQ can return the joined rows you need at the right grain.
Paging through large result sets
Section titled: Paging through large result setsLarge result sets must be paged.
$top and $skip are the paging tools:
- Request a page with
$top, with an$orderbyon a unique key. - Advance with
$skip. - Stop when a page returns fewer rows than you asked for.
Always include the $orderby on a unique key. Without an explicit order, rows can shift between pages as data changes, and you can miss or repeat records. For large extracts, prefer filtering on a changed-since date or a key range over deep $skip values, which get slower as they grow.
BAQs over REST
Section titled: BAQs over RESTA BAQ can be run over REST, which is often the cleanest way to read joined data at the grain you want. The v2 BAQ endpoint is rooted in the same company path, under a BAQ service, and returns the BAQ rows. OData options such as $filter, $select and $top can be applied to the result.
This moves query design into Kinetic, where it can be tested in the designer, and keeps the integration simple. Build the BAQ with care, because everything in BAQ fundamentals applies.
Epicor Functions over REST
Section titled: Epicor Functions over RESTPublished Epicor Functions are callable over REST under an efx path that includes the company, the library and the function name, with the function's input parameters in a JSON body. This is the preferred way to expose a custom server-side operation to an external system. See Epicor Functions, BPMs or customizations?.
Writing data through business object methods
Section titled: Writing data through business object methodsWrites go through business object methods so that the Kinetic business logic runs. The usual pattern mirrors what the screen does: get a new or existing record, change fields and call the update method, sometimes with intermediate methods that apply defaults. Tracing what the screen calls for the same action is the fastest way to find the right sequence.
For every write integration:
- Carry an external reference you can check for, so a retried call does not create a duplicate.
- Validate before calling, and log the request and response for every failed call.
- Expect business errors, such as credit holds or missing setup, to come back as errors from the method. Handle them as data problems for a person to resolve, not as retryable faults.
Troubleshooting checklist
Section titled: Troubleshooting checklistStart with these checks when a call misbehaves.
| Symptom | Check |
|---|---|
| 401 or 403 | Both credentials. A valid user with a missing or out-of-scope API key still fails |
| Empty child collections | Add $expand |
| Fewer rows than expected | Paging or server row limits |
| Field not found | The exact name in $metadata |
| Works in the browser help, fails in code | Compare headers, especially x-api-key, and the company in the URL |