Customer by { id }, { externalId } or an inline document (upserted like POST /v1/customers). Lines resolve their product by id, externalId in the channel, sku, barcode; compositions expand into their components. Totals follow the legacy rules: with items the line amounts must add up to the subtotal and the lines to the total (422 totals_mismatch with the computed values); the order total itself is refused only when totals.paid (default the total) equals the total, else stored as sent. Duplicates (externalId, then displayId, in the channel): onDuplicate update (the default) updates the existing order in the same unit (200 updated, or unchanged when nothing differs; PATCH /v1/orders/:id rules, the body read as the channel document); skip answers 200 skipped; error 409 duplicate. invoicing.generateInvoice and generatePayment create the order's invoice and record the paid amount as a payment in the same unit. Unknown fields are refused (400).
Scope: orders:write, channel:write or admin. A permission group made before the Channel API scopes existed grants it as orders:manage.
Idempotency: send Idempotency-Key; a retry with the same key replays the first answer.
Rate limit: write class, 1,200 requests per minute per tenant by default (read class 6,000). Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a refusal is 429 rate_limited with Retry-After.
