Connect a sales channel

Build a connector that keeps a marketplace, cart or B2B portal in step with Kentro: catalog, stock, orders, shipments, invoices and payments.

A channel connector moves one sales channel's data into Kentro and Kentro's answers back out: the catalog and stock the channel sells, the orders it takes, and the shipments, invoices and payments that follow. Kentro is the system of record; the connector is a system acting for one tenant through the Channel API.

Who this is for

  • Developers connecting a marketplace, a shopping cart, a B2B ordering portal or an in-house channel that Kentro's built-in integrations do not cover.
  • Partners building one connector for many Kentro tenants, with one API key per tenant.

How it fits

The connector runs on your infrastructure. It listens to the channel (webhooks or polling), writes the channel's documents into Kentro, and polls Kentro with updatedSince for what happened there: shipments, status changes, payments applied in Kentro OS. It writes those back to the channel. Kentro routes each order to warehouses and creates its fulfillment orders, unless the channel ships the order itself.

 Sales channel             Your connector                         Kentro
 (marketplace, cart,       (server, one API key                   (system of record)
  B2B portal)               per tenant)

 order placed  ----------> POST /v1/orders  -------------------->  order, routed to
                                                                   fulfillment orders
 stock, prices <---------- GET /v1/catalog  <--------------------  products, listings
 tracking      <---------- GET /v1/shipments?updatedSince=... <--  shipments made
 refund issued ----------> POST /v1/invoices, POST /v1/payments ->  refund documents

Objects involved

ObjectDirectionEndpoints
Sales channelReadGET /v1/channels
Product, variants, listingsInto Kentro, or read from KentroPOST /v1/products, PUT /v1/products/:id/listings, GET /v1/catalog
Inventory levelInto Kentro when the channel owns the count; read otherwisePUT /v1/products/:id/inventory-levels, GET /v1/catalog
CustomerInto Kentro, usually inline on the orderPOST /v1/customers
OrderInto KentroPOST /v1/orders, POST /v1/orders/import
Fulfillment orderCreated by Kentro, or generated from the channel's own splitPOST /v1/orders/:id/fulfillment-orders/generate
ShipmentRead from Kentro, or recorded when the channel shipsGET /v1/shipments, POST /v1/shipments
Invoice, paymentInto KentroPOST /v1/invoices, POST /v1/payments

Sequence

  1. Resolve the channel. GET /v1/channels (channels-list) answers the channels the key may write into; store the channelId and bind the key to that channel in Kentro OS.
  2. Sync the catalog. POST /v1/products (products-create) with channelId, externalId, sku, listings and onDuplicate: update, so the same call repeated is an update. A product with variants sends variants[], and orders then name the variant's SKU. Listings change with PUT /v1/products/:id/listings (products-listings).
  3. Agree who owns stock. When the channel owns the count, set it with PUT /v1/products/:id/inventory-levels (products-inventory-levels). When Kentro owns it, read GET /v1/catalog?channelId=...&updatedSince=..., the sellable products with prices and stock, and push the changes to the channel.
  4. Import orders. POST /v1/orders (orders-create) with the channel's order id as externalId and in the Idempotency-Key, the customer inline, lines that name a product by sku, totals, and invoicing when the channel collected payment. For a backlog, POST /v1/orders/import (orders-import) takes up to 1,000 orders and answers 202 with an operation id to poll at GET /v1/operations/:id (operations-get).
  5. Decide who fulfills. By default Kentro routes the order and creates its fulfillment orders. A channel that already split the order generates them from its own split with POST /v1/orders/:id/fulfillment-orders/generate (fulfillment-orders-generate). A channel that ships itself sends the order with "fulfillment": { "skip": true } and records each shipment with POST /v1/shipments (shipments-create).
  6. Write shipments back. Poll GET /v1/shipments?channelId=...&updatedSince=... (shipments-list) and send the carrier and tracking to the channel. GET /v1/shipments/:id/tracking (shipments-tracking) answers the tracking Kentro stores.
  7. Invoice and pay separately when needed. For net terms, deposits and B2B, leave invoicing off the order and create documents as they happen: POST /v1/invoices (invoices-create) and POST /v1/payments (payments-create) with application.mode: auto.
  8. Cancel and refund. POST /v1/orders/:id/cancel (orders-cancel) releases the reservations and cancels the open fulfillment orders. A refund is an invoice of type: refund plus a payment of type: refund, both with positive amounts.
  9. Stay in sync. Poll GET /v1/orders?updatedSince=<last updatedAt>&include=items for changes made in Kentro. Pages are newest first: walk to the end before advancing the watermark. Every step has a worked body in Common flows.

Edge cases

CaseWhat Kentro answersWhat to do
A line names a product group instead of a variant422 unresolved_reference, details[0].code: group_containerSend the variant's SKU.
A line names an unknown SKU422 unresolved_reference, details[0].code: not_foundCreate the product first, or send the line without product as a custom line (a fee, gift wrap), which never moves stock.
Totals do not add up422 totals_mismatch, with the sent and computed amounts in detailsSend what the channel charged, split into subtotal, shipping, taxes and discount.
The channel redelivers a webhookThe Idempotency-Key replays the first answer; onDuplicate: update answers unchangedNothing: no second order and no second event.
Two buyers share an email addressCustomers match by externalId, then email, then phoneSend X-Customer-Match: externalId to match by id only.
The order is canceled after units shipped409 state_conflict, details[0].code: line_shippedCancel the open fulfillment orders with POST /v1/orders/:id/fulfillment-orders/cancel and record a refund.
A payment Kentro charged must be refunded409 state_conflict, provider_refund_requiredRefund it in Kentro OS, not through the API.
An order waits in stock reviewstatus: stock-reviewThe tenant decides in Kentro, or the connector calls POST /v1/orders/:id/stock-review/accept or /reject.
The rate limit is reached429 rate_limited with Retry-AfterWait, and move backlogs to the import endpoint.

Checklist

  • The key holds channel:write, or channel:read plus the write scopes it uses, and is bound to the channel.
  • Every write sends an Idempotency-Key built from the channel's own ids.
  • Every create sends externalId and an explicit onDuplicate.
  • Money travels as decimal strings with currency, and totals add up.
  • Order lines name sellable variants.
  • History was loaded with X-Import-Mode: historical.
  • Shipments and status changes flow back through updatedSince polling.
  • 429 and 503 are retried with backoff, and every refusal's requestId is logged.
  • The Go-live checklist passes.

Build it with an AI agent

The prompt below starts a coding agent on this connector. Agent setup and the other prompts: Build with AI agents.

📘

Build it with an AI agent

Build a connector that imports a channel's products, orders and customers into Kentro and sends shipments 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 a connector between a sales channel (a marketplace or a shopping cart platform) and Kentro, using the Kentro Channel API. Kentro is the system of record for orders, inventory and fulfillment. The connector moves the channel's products, customers and orders into Kentro and sends shipments, tracking and stock back to the channel.

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-getting-started.md
- 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
- https://docs.kentro.io/docs/guide-sales-channel.md

Ask the user for these before writing code:
1. The channel platform, its API documentation and where its credentials are stored.
2. A Kentro sandbox API key pair, created in Kentro OS at https://kentro.cc under `Users` > `API keys`, with a permission group holding `channel:write` (or only the write scopes the connector needs) and bound to the target sales channel. Ask the user to store it in the environment variables KENTRO_KEY and KENTRO_SECRET. Never ask the user to paste a secret into the chat.
3. The Kentro sales channel id. If the user does not know it, call GET https://apis.kentro.dev/channel/v1/channels and ask the user to pick one.
4. Which system owns stock (the channel or Kentro), and which documents flow in each direction.

Rules:
- Build against https://apis.kentro.dev/channel/v1. Production is https://apis.kentro.live/channel/v1 with a production key, used only when the user asks.
- Call only the endpoints the Channel API reference documents. There is no Kentro SDK or CLI; use plain HTTPS.
- Send an Idempotency-Key on every write, derived from the channel's own ids (for example `<channel>-order-<id>-create`), and send the same key when you retry that write.
- Send externalId on every create, address documents as `ext:<channelId>:<externalId>`, and keep onDuplicate at update so a replayed feed never duplicates.
- Money is decimal strings in currency units ("12.50") with currency, and totals must add up. Order lines name the sellable variant, never the parent product.
- Retry only 429, 503, 500 and network errors, with backoff that honors Retry-After. Log requestId on every failure.

Steps:
1. Products: POST /v1/products with variants and the channel listing inline. When the channel owns stock, set it with PUT /v1/products/:id/inventory-levels.
2. Orders: POST /v1/orders with the customer inline, the lines, the totals and, when the channel collected payment, invoicing. Backfill history with X-Import-Mode: historical, and send large backfills to POST /v1/orders/import.
3. Fulfillment back to the channel: poll GET /v1/shipments?updatedSince=... and GET /v1/orders?updatedSince=..., walk every page to nextCursor null before advancing the watermark, and push tracking to the channel.
4. Cancellations and refunds: POST /v1/orders/:id/cancel, and refunds as the flows page describes.
5. Configuration for ids and keys (nothing hard-coded), scheduling and structured logs.

Acceptance checks. Run each one against the sandbox and show the user the requests and responses, with secrets redacted:
- GET /v1/channels answers 200 and includes the bound sales channel.
- One product POST answers 201 with outcome created; the same request again answers 200 with outcome updated or unchanged.
- One order POST with X-Dry-Run: true answers 2xx with the X-Dry-Run: applied header; the real POST then answers 201; repeating it with the same Idempotency-Key answers with Idempotent-Replayed: true.
- GET `/v1/orders/ext:<channelId>:<externalId>?include=items,customer` returns the order with the expected lines and totals.
- A shipment recorded for the order appears in GET /v1/shipments?orderId=..., and the connector pushes its tracking number to the channel.
- No secret appears in code, logs or the repository.

Did this page help you?