Fulfill as a warehouse or 3PL
Connect a WMS or 3PL to Kentro: receive fulfillment orders, report what shipped with tracking, and keep stock levels true.
A warehouse integration lets a WMS or a 3PL ship Kentro orders: it reads the fulfillment orders routed to its warehouse, reports what shipped with tracking, and keeps Kentro's stock levels true. Kentro decides routing and reserves the stock; the warehouse decides how to pick, pack and ship.
Who this is for
- WMS vendors and 3PLs that ship for a Kentro tenant through the Channel API.
- Tenants running their own warehouse system beside Kentro.
How it fits
Kentro routes each order through the tenant's routing rules and creates fulfillment orders: each one is the part of the order one warehouse ships, with its stock reserved there. The warehouse system polls for the fulfillment orders of its warehouse, ships them and reports each shipment with tracking. Fulfilling credits the order lines and moves the stock, and the order's sales channel connector reads the shipment from Kentro.
Kentro Your WMS or 3PL system
order routed -> fulfillment order ---> GET /v1/fulfillment-orders
(stock reserved at ?warehouseId=...&updatedSince=...
warehouse W) |
pick, pack, label
|
lines credited, stock moved, <--- POST /v1/fulfillment-orders/:id/fulfill
tracking stored (quantities, tracking)
stock levels updated <--- PUT /v1/products/:id/inventory-levels
Objects involved
| Object | What the warehouse does with it |
|---|---|
| Fulfillment order | The unit of work: lines, quantities, warehouse, status (pending, processing, partly-fulfilled, fulfilled, canceled, ...) and jobStatus (open, scheduled, in-progress, closed, ...). |
| Fulfillment queue | The orders waiting to ship, with their shippable fulfillment orders. |
| Shipment | What left the building: carrier, tracking, package and lines. |
| Inventory level | Units on hand per warehouse, location and lot. |
| Warehouse, location, lot | Kentro ids: warehouseId appears on every fulfillment order; locations and lots on lines and levels. |
Sequence
- Find the work. Poll
GET /v1/fulfillment-orders?warehouseId=...&status=pending&updatedSince=...(fulfillment-orders-list), or read the queue:GET /v1/fulfillment-queue?warehouseId=...(fulfillment-queue-list) lists the orders waiting to ship,GET /v1/fulfillment-queue/:id(fulfillment-queue-detail) answers one order with the lines to pack, andGET /v1/fulfillment-queue/stats(fulfillment-queue-stats) answers bounded counts. - Read one fulfillment order.
GET /v1/fulfillment-orders/:id(fulfillment-orders-get) answers it with its lines. - Report the shipment.
POST /v1/fulfillment-orders/:id/fulfill(fulfillment-orders-fulfill) withitems(lineId,orderLineIdorexternalLineId,quantity, and optionallylotIdandfromLocationId) andtracking(carrier,number,url). Quantities are deltas, so a partial shipment leaves the fulfillment order open. Use the WMS shipment id in theIdempotency-Key. - Or record shipments in batches.
POST /v1/fulfillment-queue/ship(fulfillment-queue-ship) records up to 100 shipments of queue fulfillment orders, with one result per entry.POST /v1/shipments(shipments-create) withfulfillmentOrderIdrecords one shipment with full package, rate and carrier detail. - Keep stock true.
PUT /v1/products/:id/inventory-levels(products-inventory-levels) sets units on hand absolutely, per warehouse, location and lot, with areason. Past movements load throughPOST /v1/inventory/history(inventory-history). - Handle exceptions. A fulfillment order the warehouse cannot ship moves to another warehouse with
POST /v1/fulfillment-orders/:id/reroute(fulfillment-orders-reroute), its reservation with it. Dates, holds and carrier preselection change withPATCH /v1/fulfillment-orders/:id(fulfillment-orders-update).POST /v1/fulfillment-orders/:id/cancel(fulfillment-orders-cancel) cancels one and releases its reservation. A voided label isPOST /v1/shipments/:id/cancel(shipments-cancel), which reverses exactly what the shipment recorded. - Watch for changes. Keep polling with
updatedSince: a fulfillment order canceled or rerouted in Kentro after the warehouse picked it up must stop at the warehouse too.
Edge cases
| Case | What happens | What to do |
|---|---|---|
| A partial shipment | The fulfillment order turns partly-fulfilled and stays open. | Fulfill the rest later, with a new Idempotency-Key for the new shipment. |
| More units shipped than ordered | The statuses partly-over-fulfilled and over-fulfilled record it. | Ship what the lines ask. |
| Lot-tracked stock | Fulfill lines take lotId; levels take lotNumber or lotId. | Send the lot that left the shelf. |
| The order is canceled during picking | The fulfillment order turns canceled, and its reservation is released. | Poll before shipping and stop the pick. |
| A lost answer to a fulfill call | A retry with the same Idempotency-Key replays the first answer. | Always retry with the same key. |
| Two systems record the same shipment | POST /v1/shipments with the same externalId and onDuplicate: update makes the second an update. | Send the WMS shipment id as externalId. |
| The tenant cancels an order after shipping | 409 state_conflict, line_shipped, for a cancel of the whole order. | Expected: the tenant records a refund instead. |
Checklist
- The key holds
channel:read,fulfillment:write,shipments:write, andproducts:writefor stock levels. - Polls filter by
warehouseIdandupdatedSinceand walk their pages to the end. - One
Idempotency-Keyper physical shipment. - Tracking travels with every shipment.
- Stock levels are absolute and carry a
reason; history was loaded once. - Canceled and rerouted fulfillment orders stop at the warehouse.
429and503are retried with backoff.- The Go-live checklist passes.
Build it with an AI agent
The prompt below starts a coding agent on this integration. Agent setup and the other prompts: Build with AI agents.
Build it with an AI agent
Build an integration that sends Kentro fulfillment orders to a WMS or 3PL and reports shipments, tracking and stock back. Copy the prompt into Claude Code, Codex, Cursor or another coding agent. The prompt library explains how to run it.
You are building an integration between a warehouse management system (WMS) or 3PL and Kentro, using the Kentro Channel API. Kentro routes orders into fulfillment orders. The integration hands those to the warehouse, reports what shipped with tracking, and keeps stock levels current.
Read first. Fetch https://docs.kentro.io/llms.txt and these pages, and look up every endpoint and schema in them before you use it:
- https://docs.kentro.io/docs/ai-agent-instructions.md (rules you must follow)
- https://docs.kentro.io/reference/channel-api-authentication.md
- https://docs.kentro.io/reference/channel-api-conventions.md
- https://docs.kentro.io/reference/channel-api-flows.md (sections "Decide who fulfills", "Report what the warehouse shipped", "Record a shipment" and "Keep in sync")
- https://docs.kentro.io/docs/guide-warehouse-3pl.md
Ask the user for these before writing code:
1. The WMS or 3PL, its API documentation, and how it receives orders and reports shipments (API, webhooks or files).
2. The Kentro warehouse or warehouses the integration serves, and how the WMS names them.
3. A Kentro sandbox API key pair from Kentro OS at https://kentro.cc (`Users` > `API keys`) with `channel:read`, `fulfillment:write` and `shipments:write`, plus `products:write` when the warehouse reports stock. Ask the user to store it as KENTRO_KEY and KENTRO_SECRET in the environment, never in the chat.
4. Whether the warehouse reports stock, and how often.
Rules:
- Build against https://apis.kentro.dev/channel/v1 and call only the endpoints the Channel API reference documents. There is no Kentro SDK or CLI.
- Send an Idempotency-Key on every write, derived from the WMS's shipment or event id and reused on retries, so a retried shipment report never ships twice.
- Fulfill quantities are deltas, and a partial shipment leaves the fulfillment order open.
- Never ship a fulfillment order Kentro canceled: check its status before sending work to the floor and again before reporting.
- Retry only 429, 503, 500 and network errors, with backoff that honors Retry-After. Log requestId on every failure.
Steps:
1. Pull work: list the warehouse's fulfillment orders with GET /v1/fulfillment-orders, filtered by status, updatedSince and the warehouse filter the fulfillment orders list reference names, or read GET /v1/fulfillment-queue. Follow nextCursor to null.
2. Send each fulfillment order to the WMS with its lines and ship-to address.
3. Report shipments with POST /v1/fulfillment-orders/:id/fulfill: the shipped items and the tracking (carrier, number, url).
4. Stock: PUT /v1/products/:id/inventory-levels with warehouseId, onHand and a reason.
5. Changes: poll with updatedSince for cancellations and reroutes, and stop work Kentro canceled.
Acceptance checks. Run each one against the sandbox and show the user the requests and responses, with secrets redacted:
- The integration lists at least one open fulfillment order for the warehouse. If there is none, ask the user to create a test order in the sandbox.
- A partial fulfill answers 2xx and leaves the fulfillment order open; fulfilling the rest closes it.
- The same fulfill sent again with the same Idempotency-Key answers with Idempotent-Replayed: true and ships nothing twice.
- GET /v1/shipments?orderId=... lists the shipment with its tracking number, and the order's fulfillmentStatus reflects it.
- An inventory-levels PUT shows in the product's stock afterwards.
- No secret appears in code, logs or the repository.
Updated about 12 hours ago

