Common flows

An order from import to payment, step by step, with bodies.

The flows below cover what most channel integrations do. Each step names the endpoint, the scope and the body; the endpoint reference has every field. Replace the ids with your channel's. Every write carries an Idempotency-Key; the examples omit the auth headers shown in Getting started.

1. Sync the catalog

Create or update a product with its listing on the channel. The same call repeated is an update (onDuplicate: update).

POST /v1/products
Idempotency-Key: shop-prod-2201-20261108
{
  "channelId": "64f0aabb-ccdd-1122-3344-000100000000",
  "externalId": "shop-prod-2201",
  "sku": "MUG-WHT-11",
  "barcode": "0012345678905",
  "name": "Ceramic mug, white, 11 oz",
  "unitCost": "3.20",
  "weight": { "value": 12, "unit": "oz" },
  "listings": [
    { "channelId": "64f0aabb-ccdd-1122-3344-000100000000", "isPublished": true, "unitPrice": "12.50" }
  ],
  "onDuplicate": "update"
}

A product with variants sends variants[] on the parent; each variant has its own sku, externalId and options ({ "Size": "M" }). Orders then name the variant's SKU, never the parent's.

Set stock at a warehouse when the channel owns the inventory count:

PUT /v1/products/ext:64f0aabb-ccdd-1122-3344-000100000000:shop-prod-2201/inventory-levels
{ "levels": [ { "warehouseId": "5f1f53a9-b4eb-f600-11c3-b1b500000000", "onHand": 120, "reason": "Daily sync" } ] }

Read what the channel may sell, with prices and stock, from GET /v1/catalog?channelId=....

2. Import an order

One call creates the order, its customer (inline, matched by externalId, email or phone) and, when asked, its invoice and payment.

POST /v1/orders
Idempotency-Key: shop-1001
{
  "channelId": "64f0aabb-ccdd-1122-3344-000100000000",
  "externalId": "shop-1001",
  "displayId": "#1001",
  "externalCreatedAt": "2026-11-08T14:03:00Z",
  "customer": { "externalId": "shop-cust-88412", "email": "[email protected]", "firstName": "Ada", "lastName": "Lovelace" },
  "shipTo": { "name": "Ada Lovelace", "address1": "1 Analytical Way", "city": "San Francisco", "stateProvince": "CA", "postalCode": "94105", "country": "US" },
  "items": [
    { "externalLineId": "li-1", "product": { "sku": "MUG-WHT-11" }, "quantity": 2, "unitPrice": "12.50" },
    { "externalLineId": "li-2", "name": "Gift wrap", "sku": "GIFTWRAP", "quantity": 1, "unitPrice": "3.00", "requiresShipping": false }
  ],
  "totals": { "currency": "USD", "subtotal": "28.00", "shipping": "5.00", "salesTax": "2.45", "total": "35.45", "paid": "35.45" },
  "invoicing": { "generateInvoice": true, "generatePayment": true },
  "onDuplicate": "update"
}
  • A line with product must resolve to a sellable product (sku, barcode, externalId or id); an unknown one is 422 unresolved_reference with details[0].code: not_found. A line without product is a custom line (a fee, gift wrap) and never moves stock.
  • totals.paid is what the channel collected. With invoicing.generateInvoice and generatePayment the order gets its invoice and a payment for paid, applied to the invoice.
  • The answer is 201 { data: <order>, outcome: "created" }. data.fulfillmentStatus starts at pending once Kentro routes the order; data.financialStatus is paid.

Read it back any time, with its lines and customer:

GET /v1/orders/ext:64f0aabb-ccdd-1122-3344-000100000000:shop-1001?include=items,customer

3. Decide who fulfills

By default Kentro routes the order through the tenant's routing rules and creates its fulfillment orders. Two other patterns:

The channel already split the order (a marketplace that tells you which location ships what): generate the fulfillment orders from the channel's own split.

POST /v1/orders/ext:64f0aabb-ccdd-1122-3344-000100000000:shop-1001/fulfillment-orders/generate
{
  "fulfillmentOrders": [
    { "externalId": "shop-fo-1001-1", "warehouseExternalId": "sf-warehouse", "items": [ { "externalLineId": "li-1", "quantity": 2 } ] }
  ]
}

The channel ships the order itself and Kentro only records it: send the order with "fulfillment": { "skip": true }, then record the shipment (step 5).

GET /v1/fulfillment-orders?orderId=... lists an order's fulfillment orders with their status, jobStatus, warehouse and lines.

4. Report what the warehouse shipped

When your system is the warehouse or 3PL, fulfill the fulfillment order with the shipped quantities and the tracking. Quantities are deltas; a partial shipment leaves the fulfillment order open.

POST /v1/fulfillment-orders/ext:64f0aabb-ccdd-1122-3344-000100000000:shop-fo-1001-1/fulfill
Idempotency-Key: wms-ship-5590
{
  "items": [ { "externalLineId": "li-1", "quantity": 2 } ],
  "tracking": [ { "carrier": "UPS", "number": "1Z999AA10123456784", "url": "https://www.ups.com/track?tracknum=1Z999AA10123456784" } ]
}

5. Record a shipment

A shipment made outside Kentro (by the channel, a 3PL or a label bought elsewhere) is recorded on the order. It credits the lines it names and, by default, moves the stock.

POST /v1/shipments
Idempotency-Key: shop-ship-5590
{
  "externalId": "shop-ship-5590",
  "orderId": "ext:64f0aabb-ccdd-1122-3344-000100000000:shop-1001",
  "shippedAt": "2026-11-09T18:20:00Z",
  "carrier": { "name": "UPS", "serviceLevel": "Ground" },
  "trackingNumber": "1Z999AA10123456784",
  "package": { "weight": "1.6", "weightUnit": "lb", "length": 8, "width": 6, "height": 5, "dimensionUnit": "in" },
  "shippingCharge": "5.00",
  "items": [ { "externalLineId": "li-1", "quantity": 2 } ],
  "onDuplicate": "update"
}

A label bought through Kentro is linked instead of described: "postage": { "shipmentId": "<carrier shipment id>" }. GET /v1/shipments/:id/tracking answers the carrier events Kentro has.

Shipments Kentro makes (labels bought in the Kentro portal, fulfillment orders fulfilled by the warehouse) appear in GET /v1/shipments?orderId=... without any call from the integration; poll with updatedSince or read the order's fulfillmentStatus.

6. Invoice and pay separately

When the channel does not settle at order time (net terms, deposits, B2B), skip invoicing on the order and create the documents as they happen.

POST /v1/invoices
Idempotency-Key: shop-inv-7001
{
  "channelId": "64f0aabb-ccdd-1122-3344-000100000000",
  "externalId": "shop-inv-7001",
  "type": "charge",
  "order": { "externalId": "shop-1001" },
  "items": [ { "externalLineId": "li-1", "quantity": 2, "unitPrice": "12.50" } ],
  "totals": { "currency": "USD", "subtotal": "25.00", "shipping": "5.00", "salesTax": "2.45", "total": "32.45" },
  "dueDate": "2026-12-08"
}
POST /v1/payments
Idempotency-Key: shop-pay-9001
{
  "channelId": "64f0aabb-ccdd-1122-3344-000100000000",
  "externalId": "shop-pay-9001",
  "method": "card",
  "amount": "32.45",
  "currency": "USD",
  "order": { "externalId": "shop-1001" },
  "transactionId": "ch_3Q7dXYZ",
  "application": { "mode": "auto" }
}

application.mode: auto applies the payment to the order's open invoices oldest first. manual with invoices: [{ invoiceId, amount }] applies exact amounts; POST /v1/payments/:id/apply does the same later, and POST /v1/payments/applications/:id/unapply reverses one application. A refund is an invoice of type: refund plus a payment of type: refund, both with positive amounts.

7. Cancel

POST /v1/orders/ext:64f0aabb-ccdd-1122-3344-000100000000:shop-1001/cancel
Idempotency-Key: shop-1001-cancel
{ "reason": "Customer request" }

Cancel releases the reservations, cancels the open fulfillment orders and answers 200 { outcome: "updated" }; cancelling an already canceled order is 200 unchanged. An order with shipped units cannot be canceled as a whole (409 state_conflict, details[0].code: line_shipped): cancel its open fulfillment orders with POST /v1/orders/:id/fulfillment-orders/cancel and record the refund instead. A payment Kentro itself charged is refunded through the Kentro portal, not through the API (409 state_conflict, provider_refund_required).

8. Keep in sync

Poll GET /v1/orders?updatedSince=<last updatedAt>&include=items (and the same on shipments, fulfillment orders, invoices and payments) to pick up what changed in Kentro: shipments the warehouse made, status changes, payments applied in the portal. Pass the latest updatedAt seen as the next updatedSince; the page is newest first, so walk it to the end before advancing the watermark.

9. Retailer purchase orders

A retailer PO received outside Kentro's EDI connections is created, then accepted or rejected:

POST /v1/purchase-orders
Idempotency-Key: po-4500262852
{
  "channelId": "64f0aabb-ccdd-1122-3344-000100000000",
  "externalId": "4500262852",
  "purchaseOrderNumber": "4500262852",
  "purchaseOrderDate": "2026-11-08",
  "provider": "sps",
  "customer": { "externalId": "retailer-dc-14" },
  "shipTo": { "name": "Retailer DC 14", "address1": "900 Distribution Dr", "city": "Reno", "stateProvince": "NV", "postalCode": "89502", "country": "US" },
  "items": [ { "externalLineId": "1", "product": { "sku": "MUG-WHT-11" }, "quantity": 240, "unitPrice": "6.10" } ],
  "totals": { "currency": "USD", "subtotal": "1464.00", "total": "1464.00" },
  "shipNotBefore": "2026-11-15",
  "shipNotAfter": "2026-11-22",
  "onDuplicate": "error"
}

POST /v1/purchase-orders/:id/accept turns it into a sales order that Kentro routes and ships; POST /v1/purchase-orders/:id/reject with { "reason": "..." } declines it.