Skip to content

Calling the Kinetic REST v2 API

  • Epicor Kinetic

How-toIntermediate5 min read

View Markdown

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.

Business 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=10

Business 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.

A 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:

Terminal window
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 $orderby

The 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 $expand

This 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,CustNum

A 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 sets

Large result sets must be paged.

$top and $skip are the paging tools:

  1. Request a page with $top, with an $orderby on a unique key.
  2. Advance with $skip.
  3. 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.

A 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.

Published 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 methods

Writes 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.

Start 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

Sources