What changed, call by call, between api.kentro.live/v1/channel/* and the Channel API.
The legacy Channel API (https://api.kentro.live/v1/channel/<service>) keeps answering for existing integrations, but it is deprecated: it receives no new fields or endpoints, its reference is no longer published, and it will be retired once every integration has moved. New integrations use the Channel API at https://apis.kentro.live/channel/v1/*; existing ones migrate with this page, whose call-by-call table is the record of what each legacy call did.
What stays the same
- The API key. The same
kentro-keyandkentro-secretpair works on both. A key that holdsadminor the olderorders:manage,invoices:manage,payments:manageandfulfillment-orders:managescopes keeps those grants; see Scopes for the finer-grained ones. - The data. Both APIs read and write the same records. A document created through the legacy API is readable through the new one by its legacy
objectId(GET /v1/orders/6512aa00bb11cc22dd33ee44), and the other way round. - Idempotency keys. The legacy
?idempotency=query parameter is accepted as an alias of theIdempotency-Keyheader.
What changed
| Legacy | Channel API |
|---|---|
Host api.kentro.live, path /v1/channel/<resource>/<verb> | Host apis.kentro.live, path /channel/v1/<resource> with HTTP verbs (POST create, PATCH update, DELETE, POST /:id/cancel). |
$channel=<id> in the query string on every call | channelId in the body of a create; the document's own channel afterwards. |
Request bodies as an array of one record ([{ ... }]) | One JSON object per request. |
objectId, *ObjectId and *ExternalId fields to address records | id, or ext:<channelId>:<externalId> wherever an id is taken (path or body). |
Money in cents (unitPrice: 1250) | Decimal strings in currency units ("unitPrice": "12.50") with currency. |
duplicatesAction: error / skip / update | onDuplicate: error / skip / update, default update. |
Status answers such as { status: 'created', objectId } | { data: <full document>, outcome: 'created' }. |
Feathers errors ({ name, message, code, className }, often 422 Unprocessable for everything) | One envelope, { error: { code, message, details, requestId } }, with a stable code list. |
$skip / $limit paging with totals | Cursor paging: limit, nextCursor; no totals. |
| Writes accepted unknown fields silently | Unknown fields are 400 validation_failed. |
| Legacy created orders onto an unsellable product group when the line named the parent | 422 unresolved_reference with details[0].code: group_container: name the variant. |
Call by call
| Legacy call | Replacement |
|---|---|
customer/create | POST /v1/customers |
customer/update | PATCH /v1/customers/:id, or POST /v1/customers with onDuplicate: update |
customer/delete | DELETE /v1/customers/:id |
list-channels | GET /v1/channels |
product/create, product/update | POST /v1/products (variants and listings inline; id plus onDuplicate: update for an update), PUT /v1/products/:id/inventory-levels for stock |
product/delete | DELETE /v1/products/:id |
product/details | GET /v1/products/:id, or GET /v1/products?sku=...&channelId=... |
product/list | GET /v1/products?channelId=...&include=variants,listings |
product/catalog | GET /v1/catalog?channelId=... |
product/update-identifiers | PUT /v1/products/:id/identifiers |
product/update-listing | PUT /v1/products/:id/listings |
product/update-inventory-level | PUT /v1/products/:id/inventory-levels |
product/apply-price-list | POST /v1/products/:id/price-list |
order/create | POST /v1/orders (embedded fulfillment orders: POST /v1/orders/:id/fulfillment-orders/generate) |
order/update | PATCH /v1/orders/:id; createIfDoesNotExist is POST /v1/orders with onDuplicate: update |
order/cancel | POST /v1/orders/:id/cancel |
order/delete | DELETE /v1/orders/:id |
order/create-invoice | POST /v1/orders/:id/invoices |
fulfillment-order/create | POST /v1/fulfillment-orders |
fulfillment-order/generate | POST /v1/orders/:id/fulfillment-orders/generate |
fulfillment-order/update | PATCH /v1/fulfillment-orders/:id |
fulfillment-order/fulfill | POST /v1/fulfillment-orders/:id/fulfill |
fulfillment-order/cancel | POST /v1/fulfillment-orders/:id/cancel; by order, POST /v1/orders/:id/fulfillment-orders/cancel |
fulfillment-order/delete | DELETE /v1/fulfillment-orders/:id; by order, DELETE /v1/orders/:id/fulfillment-orders |
fulfillment-order/refresh | POST /v1/orders/:id/fulfillment-orders/refresh |
fulfillment-order/reroute | POST /v1/fulfillment-orders/:id/reroute |
fulfillment-order/resubmit | POST /v1/fulfillment-orders/:id/resubmit |
fulfillment-queue/list | GET /v1/fulfillment-queue |
fulfillment-queue/detail | GET /v1/fulfillment-queue/:orderId |
fulfillment-queue/stats | GET /v1/fulfillment-queue/stats |
fulfillment-queue/ship | POST /v1/fulfillment-queue/ship, or POST /v1/shipments |
invoice/create | POST /v1/invoices |
invoice/update | PATCH /v1/invoices/:id |
invoice/cancel | POST /v1/invoices/:id/cancel |
invoice/delete | DELETE /v1/invoices/:id |
payment/create | POST /v1/payments (application.mode applies it) |
payment/update | PATCH /v1/payments/:id |
payment/apply | POST /v1/payments/:id/apply |
payment/unapply | POST /v1/payments/applications/:id/unapply |
payment/cancel | POST /v1/payments/:id/cancel; with action: delete, DELETE /v1/payments/:id |
payment/delete | DELETE /v1/payments/:id |
po/create | POST /v1/purchase-orders |
po/update | PATCH /v1/purchase-orders/:id |
po/accept | POST /v1/purchase-orders/:id/accept |
po/reject | POST /v1/purchase-orders/:id/reject |
po/cancel | POST /v1/purchase-orders/:id/cancel |
po/delete | DELETE /v1/purchase-orders/:id |
shipping/create | POST /v1/shipments (postage.shipmentId links a label bought through Kentro) |
shipping/update | PATCH /v1/shipments/:id |
shipping/cancel | POST /v1/shipments/:id/cancel |
shipping/delete | DELETE /v1/shipments/:id |
shipping/track | GET /v1/shipments/:id/tracking; by order, GET /v1/shipments?orderId=... |
Behavior the new API refuses on purpose
The legacy API accepted some inputs that produced records nobody could act on. The new API answers a refusal instead; each is listed with the code to look for.
- A product's parent group named on an order line:
422 unresolved_reference,group_container. Send the variant. - A cancel of an order with shipped units:
409 state_conflict,line_shipped. Cancel the open fulfillment orders and record a refund. - A cancel or delete of a payment that Kentro charged itself:
409 state_conflict,provider_refund_required. Refund it in the Kentro portal. - A delete of a product that holds stock:
409 state_conflict,holds_stock. - An invoice or payment whose totals do not add up:
422 totals_mismatch, with both amounts indetails.
A migration plan that has worked
- Create a new API key for the integration with a permission group that holds
channel:write(or the per-resource scopes it needs), bound to the channels it serves. - Point reads at the new API first (
GETendpoints, polling withupdatedSince). Reads have no side effects and show you the new document shapes. - Move one write at a time, starting with
POST /v1/customersandPOST /v1/products, replaying your feed withX-Dry-Run: trueuntil every document answers2xx; then without it. - Move
POST /v1/orders; keeponDuplicate: updateso a document the legacy integration already created is updated, not duplicated. - Move shipments, invoices and payments; retire the legacy calls; revoke the legacy integration's key when its last call is gone.
Questions during a migration go to [email protected] with the requestId of any refusal.
