Sync an ERP or accounting system

Keep an ERP or accounting system in step with Kentro: customers, products and costs, stock, invoices, payments and vendors.

An ERP sync keeps the master data and books in an ERP or accounting system consistent with Kentro's commerce documents. Kentro records orders, fulfillment, invoices and payments as they happen; the ERP usually owns accounting, costs and cash, and either side can own customers, products and stock.

Who this is for

  • Developers connecting an ERP or an accounting system to a Kentro tenant through the Channel API.
  • Kentro OS has built-in QuickBooks Online and QuickBooks Desktop connections. This guide is for systems those do not cover, and for syncs that need their own rules.

Decide who owns what

Write the ownership down before writing code: one owner per field keeps two systems from overwriting each other.

DataUsual ownerDirectionEndpoints
Customers and payment termsERP or KentroEitherPOST /v1/customers, GET /v1/customers
Products, SKUs and unit costsERPERP to KentroPOST /v1/products, POST /v1/products/:id/price-list
Stock on handKentro's warehouses, or the ERPEitherPUT /v1/products/:id/inventory-levels, GET /v1/catalog
OrdersKentroKentro to ERPGET /v1/orders
InvoicesKentro, or the ERP for invoices raised thereEitherGET /v1/invoices, POST /v1/invoices
PaymentsThe ERP for cash receipts; Kentro for payments it chargedEitherPOST /v1/payments, GET /v1/payments
VendorsERPERP to KentroPOST /v1/vendors

How it fits

A scheduled sync service pushes master data and cash receipts from the ERP into Kentro, and pulls Kentro's orders, invoices and payments into the ERP by polling with updatedSince.

 ERP or accounting system          Your sync service                    Kentro
                                   (scheduled worker)

 customers, products, costs ---->  POST /v1/customers, /v1/products -->  master data
 vendors                    ---->  POST /v1/vendors  --------------->  vendors
 cash receipts              ---->  POST /v1/payments --------------->  applied to invoices
 sales, invoices, payments  <----  GET /v1/orders, /v1/invoices,  <--  commerce documents
                                   /v1/payments ?updatedSince=...

Objects involved

ObjectNotes
CustomerMatched by externalId, then email, then phone; X-Customer-Match: externalId matches by id only.
Productsku, unitCost, variants, listings and prices per sales channel.
Inventory levelAbsolute onHand per warehouse, location and lot, with a reason.
OrderRead-only for an ERP sync in most designs.
Invoicetype: charge or refund; totals must add up; dueDate is a date.
Paymentmethod, amount and currency, applied to invoices automatically or by amount.
VendorName, address, payment terms and contacts.

Sequence

  1. Load history once. Import past documents with X-Import-Mode: historical, so they send no confirmation emails, packing slips, accounting exports or EDI documents. Past stock movements go to POST /v1/inventory/history (inventory-history): up to 500 ledger rows per call, with no counter moves and no events.
  2. Push master data. Customers with POST /v1/customers (customers-create), products and costs with POST /v1/products (products-create), prices with POST /v1/products/:id/price-list (products-price-list), suppliers with POST /v1/vendors (vendors-create). Send the ERP's id as externalId and keep onDuplicate: update, so a nightly full push is an upsert.
  3. Keep stock true. When the ERP owns stock, set it with PUT /v1/products/:id/inventory-levels (products-inventory-levels). When Kentro's warehouses own it, read it from GET /v1/catalog.
  4. Pull commerce documents. Poll GET /v1/orders, GET /v1/invoices and GET /v1/payments with updatedSince (and channelId to split by channel), and post them to the ERP. Keep a map of Kentro id to ERP id; ext:<channelId>:<externalId> addresses a document by its external id. See Common flows.
  5. Push invoices raised in the ERP. POST /v1/invoices (invoices-create) with externalId, the order it bills, lines and totals. Later changes go through PATCH /v1/invoices/:id (invoices-update) with version; POST /v1/invoices/:id/cancel (invoices-cancel) cancels one.
  6. Record cash receipts. POST /v1/payments (payments-create) with application: { "mode": "auto" } applies the payment to the order's open invoices, oldest first; mode: "manual" with invoices: [{ invoiceId, amount }] applies exact amounts. POST /v1/payments/:id/apply (payments-apply) applies what is left later, and POST /v1/payments/applications/:id/unapply (payments-unapply-application) reverses one application.
  7. Record refunds. A refund is an invoice of type: refund plus a payment of type: refund, both with positive amounts.

Edge cases

CaseWhat happensWhat to do
Amount precisionMoney is a decimal string in currency units; more decimals than the currency allows is 400 validation_failed, detail code precision.Round in the ERP before sending.
Totals that do not add up422 totals_mismatch, with the sent and computed amounts.Send the ERP's own breakdown of subtotal, shipping, taxes and discount.
A payment names invoices but no orderApplied to their order when every named invoice belongs to one order in the payment's sales channel; invoices of several orders still need the order named (409).Name the order on multi-order receipts, or split the receipt.
A payment Kentro chargedCancel and delete answer 409 state_conflict, provider_refund_required.Refund it in Kentro OS; record only the ERP side.
An invoice with paymentsCancel and delete are refused.Unapply the payments first, or issue a refund invoice.
Concurrent editsA stale version on PATCH answers 409 version_conflict.Read again and re-apply, or leave version out where the ERP owns every field it sends.
Customers sharing an email addressMatching by email merges them into one.Send X-Customer-Match: externalId.
Date-only fieldsdueDate and the other date fields take YYYY-MM-DD, anchored at noon UTC.Send dates, not timestamps.

Checklist

  • Ownership is written down per field, and each sync direction honors it.
  • The key holds channel:read plus only the write scopes the sync uses.
  • History was loaded with X-Import-Mode: historical.
  • ERP ids travel as externalId; master data uses onDuplicate: update.
  • Every write sends an Idempotency-Key built from the ERP document id.
  • Each document type keeps its own updatedSince watermark, and polls walk their pages to the end.
  • Payments Kentro charged are refunded in Kentro OS, never cancelled through the API.
  • The Go-live checklist passes.

Build it with an AI agent

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

📘

Build it with an AI agent

Build a sync for customers, invoices and payments between Kentro and an ERP or accounting system, without duplicates. Copy the prompt into Claude Code, Codex, Cursor or another coding agent. The prompt library explains how to run it.

You are building a sync between Kentro and the user's ERP or accounting system, using the Kentro Channel API. It moves customers, invoices and payments in the directions the user chooses, and keeps both systems consistent without duplicates.

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 "Invoice and pay separately" and "Keep in sync")
- https://docs.kentro.io/docs/guide-erp-accounting.md

Ask the user for these before writing code:
1. The ERP or accounting system, its API documentation and where its credentials are stored.
2. For each document type (customers, orders, invoices, payments), which system creates it and which system receives it.
3. A Kentro sandbox API key pair from Kentro OS at https://kentro.cc (`Users` > `API keys`) with only the scopes the sync needs: `channel:read` to read from Kentro, and `customers:write`, `invoices:write` and `payments:write` to write into Kentro. Ask the user to store it as KENTRO_KEY and KENTRO_SECRET in the environment, never in the chat.
4. The Kentro sales channel id the documents belong to (GET https://apis.kentro.dev/channel/v1/channels lists them) and the currency.
5. How the ERP's customer, invoice and payment ids map to Kentro externalId values.

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 ERP document id and version, and reuse it on retries. Send externalId on every create and keep onDuplicate at update.
- Money is decimal strings in currency units with currency. Invoice and payment totals must add up (422 totals_mismatch otherwise). Use decimal arithmetic, never floats.
- Date-only fields such as dueDate take YYYY-MM-DD.
- When several ERP customers share an email address, send X-Customer-Match: externalId so they stay separate Kentro customers.
- A refund is an invoice of type refund plus a payment of type refund, both with positive amounts. A payment Kentro charged itself is refunded in the Kentro portal, not through the API (409 state_conflict with provider_refund_required): report those to the user instead of retrying.
- Retry only 429, 503, 500 and network errors, with backoff that honors Retry-After. Log requestId on every failure.

Steps:
1. Customers: POST /v1/customers as an upsert, or read Kentro's customers with GET /v1/customers?updatedSince=... when Kentro is the source.
2. Invoices: POST /v1/invoices with the order reference, lines, totals and dueDate, or export Kentro's invoices with GET /v1/invoices?updatedSince=....
3. Payments: POST /v1/payments with application.mode auto (oldest open invoices first) or manual with exact invoice amounts; POST /v1/payments/:id/apply applies one later.
4. Incremental export: one watermark per resource; follow nextCursor to null before advancing it.
5. A run report: documents created, updated, unchanged and failed, with the requestId of each failure.

Acceptance checks. Run each one against the sandbox and show the user the requests and responses, with secrets redacted:
- The same customer sent twice answers 201 created, then 200 updated or unchanged.
- An invoice whose totals add up answers 201; a copy with a wrong total answers 422 totals_mismatch, and the sync reports it without retrying.
- A payment with application.mode auto is applied to the open invoice; read it back with GET /v1/payments/:id.
- A write repeated with the same Idempotency-Key answers with Idempotent-Replayed: true.
- An export run picks up a document changed in Kentro since the last watermark, and a second run with no changes writes nothing.
- No secret appears in code, logs or the repository.

Did this page help you?