Migrating from the legacy Channel API

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-key and kentro-secret pair works on both. A key that holds admin or the older orders:manage, invoices:manage, payments:manage and fulfillment-orders:manage scopes 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 the Idempotency-Key header.

What changed

LegacyChannel 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 callchannelId 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 recordsid, 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 / updateonDuplicate: 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 totalsCursor paging: limit, nextCursor; no totals.
Writes accepted unknown fields silentlyUnknown fields are 400 validation_failed.
Legacy created orders onto an unsellable product group when the line named the parent422 unresolved_reference with details[0].code: group_container: name the variant.

Call by call

Legacy callReplacement
customer/createPOST /v1/customers
customer/updatePATCH /v1/customers/:id, or POST /v1/customers with onDuplicate: update
customer/deleteDELETE /v1/customers/:id
list-channelsGET /v1/channels
product/create, product/updatePOST /v1/products (variants and listings inline; id plus onDuplicate: update for an update), PUT /v1/products/:id/inventory-levels for stock
product/deleteDELETE /v1/products/:id
product/detailsGET /v1/products/:id, or GET /v1/products?sku=...&channelId=...
product/listGET /v1/products?channelId=...&include=variants,listings
product/catalogGET /v1/catalog?channelId=...
product/update-identifiersPUT /v1/products/:id/identifiers
product/update-listingPUT /v1/products/:id/listings
product/update-inventory-levelPUT /v1/products/:id/inventory-levels
product/apply-price-listPOST /v1/products/:id/price-list
order/createPOST /v1/orders (embedded fulfillment orders: POST /v1/orders/:id/fulfillment-orders/generate)
order/updatePATCH /v1/orders/:id; createIfDoesNotExist is POST /v1/orders with onDuplicate: update
order/cancelPOST /v1/orders/:id/cancel
order/deleteDELETE /v1/orders/:id
order/create-invoicePOST /v1/orders/:id/invoices
fulfillment-order/createPOST /v1/fulfillment-orders
fulfillment-order/generatePOST /v1/orders/:id/fulfillment-orders/generate
fulfillment-order/updatePATCH /v1/fulfillment-orders/:id
fulfillment-order/fulfillPOST /v1/fulfillment-orders/:id/fulfill
fulfillment-order/cancelPOST /v1/fulfillment-orders/:id/cancel; by order, POST /v1/orders/:id/fulfillment-orders/cancel
fulfillment-order/deleteDELETE /v1/fulfillment-orders/:id; by order, DELETE /v1/orders/:id/fulfillment-orders
fulfillment-order/refreshPOST /v1/orders/:id/fulfillment-orders/refresh
fulfillment-order/reroutePOST /v1/fulfillment-orders/:id/reroute
fulfillment-order/resubmitPOST /v1/fulfillment-orders/:id/resubmit
fulfillment-queue/listGET /v1/fulfillment-queue
fulfillment-queue/detailGET /v1/fulfillment-queue/:orderId
fulfillment-queue/statsGET /v1/fulfillment-queue/stats
fulfillment-queue/shipPOST /v1/fulfillment-queue/ship, or POST /v1/shipments
invoice/createPOST /v1/invoices
invoice/updatePATCH /v1/invoices/:id
invoice/cancelPOST /v1/invoices/:id/cancel
invoice/deleteDELETE /v1/invoices/:id
payment/createPOST /v1/payments (application.mode applies it)
payment/updatePATCH /v1/payments/:id
payment/applyPOST /v1/payments/:id/apply
payment/unapplyPOST /v1/payments/applications/:id/unapply
payment/cancelPOST /v1/payments/:id/cancel; with action: delete, DELETE /v1/payments/:id
payment/deleteDELETE /v1/payments/:id
po/createPOST /v1/purchase-orders
po/updatePATCH /v1/purchase-orders/:id
po/acceptPOST /v1/purchase-orders/:id/accept
po/rejectPOST /v1/purchase-orders/:id/reject
po/cancelPOST /v1/purchase-orders/:id/cancel
po/deleteDELETE /v1/purchase-orders/:id
shipping/createPOST /v1/shipments (postage.shipmentId links a label bought through Kentro)
shipping/updatePATCH /v1/shipments/:id
shipping/cancelPOST /v1/shipments/:id/cancel
shipping/deleteDELETE /v1/shipments/:id
shipping/trackGET /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 in details.

A migration plan that has worked

  1. 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.
  2. Point reads at the new API first (GET endpoints, polling with updatedSince). Reads have no side effects and show you the new document shapes.
  3. Move one write at a time, starting with POST /v1/customers and POST /v1/products, replaying your feed with X-Dry-Run: true until every document answers 2xx; then without it.
  4. Move POST /v1/orders; keep onDuplicate: update so a document the legacy integration already created is updated, not duplicated.
  5. 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.