{"endpoint":"/api/mcp","transport":"streamable-http","authentication":"Bearer token (tpp_live_* / tpp_test_*) — the same per-sub-brand API key as /api/v1/*, forwarded verbatim.","tool_count":28,"tools":[{"name":"adjust_customer_coins","title":"Adjust a customer's coin balance","description":"Adjust a customer's coin balance\n\nCredits (positive `delta`) or debits (negative `delta`) the\ncustomer's virtual-coin wallet. Runs inside a transaction with\n`SELECT … FOR UPDATE` so concurrent debits cannot both pass the\nnon-negative balance guard.\n\n**Idempotency-Key is required.** Replaying with the same key +\nthe same body returns the cached response (the balance from the\noriginal execution, NOT a fresh read). Replaying with a different\nbody returns 409 `idempotency_conflict`.\n\nConstraints:\n- `delta` is a signed integer, non-zero, `|delta| <= 1_000_000`.\n- Debits that would leave the balance negative return 409 `conflict`\n  with `balance_before` in the error extras.\n\nRequires API key scope: customers:coins.","method":"POST","path":"/api/v1/customers/{id}/coins","scopes":["customers:coins"],"idempotent":true,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."},"delta":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reason":{"type":"string","maxLength":500},"idempotency_key":{"description":"Optional. Reuse the same value to safely retry this exact call without duplicating its effect. Auto-generated when omitted.","type":"string","minLength":1,"maxLength":64}},"required":["id","delta"]}},{"name":"bulk_adjust_customer_coins","title":"Bulk-adjust coins over a customer segment","description":"Bulk-adjust coins over a customer segment\n\nCredits (positive `delta`) or debits (negative `delta`) the coin\nwallet of every customer matching `filter`, scoped to the calling\nkey's sub-brand.\n\nThe filter targets almost any customer attribute: `status`,\n`segment_id`, `country`, registration/creation date windows\n(`created_after`/`before`, `registered_after`/`before`), coin\nbalance range (`min_coins`/`max_coins`) and lifetime order count\n(`min_orders`/`max_orders`), plus free-text `q`.\n\n**Idempotency-Key is required.** The whole batch is one\ntransaction, so a retry after a partial failure re-applies cleanly\n(no double-credit).\n\nConstraints:\n- A non-empty filter is required (no accidental credit-everyone).\n- A match set larger than 1000 is rejected — narrow the filter.\n- Debits that would push a wallet negative are skipped and listed\n  in `skipped`.\n- Send `dry_run: true` to preview `matched_count` + a `sample`\n  before committing.\n\nRequires API key scope: customers:coins.","method":"POST","path":"/api/v1/customers/bulk/coins","scopes":["customers:coins"],"idempotent":true,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"filter":{"type":"object","properties":{"q":{"type":"string","minLength":1,"maxLength":120},"status":{"type":"string","enum":["active","watch","restricted","banned"]},"segment_id":{"type":"string","minLength":1,"maxLength":36},"country":{"type":"string","minLength":2,"maxLength":2},"created_after":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"created_before":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"registered_after":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"registered_before":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"min_coins":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"max_coins":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"min_orders":{"type":"integer","minimum":0,"maximum":9007199254740991},"max_orders":{"type":"integer","minimum":0,"maximum":9007199254740991}}},"delta":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reason":{"type":"string","maxLength":500},"dry_run":{"default":false,"type":"boolean"},"idempotency_key":{"description":"Optional. Reuse the same value to safely retry this exact call without duplicating its effect. Auto-generated when omitted.","type":"string","minLength":1,"maxLength":64}},"required":["filter","delta"]}},{"name":"cancel_order","title":"Cancel an order","description":"Cancel an order\n\nTransitions the order to `cancelled`. Only valid while the\norder is in `pending` or `processing` — later states return 409\n`conflict` with the current status in the error extras.\n\nCancellation atomically releases or refunds TPP billing saldo and\nrestores any storefront points debited for the order. Concurrent\nrequests cannot apply the compensation twice.\n\nRequires API key scope: orders:delete.","method":"DELETE","path":"/api/v1/orders/{id}","scopes":["orders:delete"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"create_customer","title":"Create a customer","description":"Create a customer\n\nCreates a customer in the calling key's sub-brand. The\n`Idempotency-Key` header is **required** — retries with the\nsame key + same body return the cached response; retries with\nthe same key + a different body return 409 `idempotency_conflict`.\n\nIf `external_id` collides with an existing customer in this\nsub-brand, returns 409 `conflict`.\n\nRequires API key scope: customers:create.","method":"POST","path":"/api/v1/customers","scopes":["customers:create"],"idempotent":true,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"external_id":{"description":"Caller-supplied identifier. Unique per sub-brand — duplicates return 409 conflict.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"segment_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}]},"first_name":{"type":"string","minLength":1,"maxLength":120},"last_name":{"type":"string","minLength":1,"maxLength":120},"nickname":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"email":{"anyOf":[{"type":"string","maxLength":255,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"address_line":{"anyOf":[{"type":"string","maxLength":240},{"type":"null"}]},"postal_code":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"zone":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"locality":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"city":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"state":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"country":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}]},"preferred_lang":{"anyOf":[{"type":"string","enum":["en","es","pt","it","fr"]},{"type":"null"}]},"platform_registered_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"status":{"type":"string","enum":["active","watch","restricted","banned"]},"idempotency_key":{"description":"Optional. Reuse the same value to safely retry this exact call without duplicating its effect. Auto-generated when omitted.","type":"string","minLength":1,"maxLength":64}},"required":["first_name","last_name"]}},{"name":"create_store_session","title":"Create a storefront hand-off (SSO)","description":"Create a storefront hand-off (SSO)\n\nMints a short-lived, signed hand-off URL that logs the given CRM\nuser into the storefront. Resolve order: `external_id` then\n`email`, scoped to the calling key's sub-brand. Unknown users are\ncreated just-in-time.\n\nReturns 404 when `store_id` does not belong to the sub-brand.\n\nRequires API key scope: store_sessions:create.","method":"POST","path":"/api/v1/store-sessions","scopes":["store_sessions:create"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"store_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Storefront to hand the user off to. Must belong to the calling key's sub-brand."},"external_id":{"type":"string","minLength":1,"maxLength":120,"description":"The CRM's identifier for the user. Resolved against `customers.external_id` within the sub-brand; created just-in-time if unknown."},"first_name":{"description":"Used only when JIT-creating the customer.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"last_name":{"description":"Used only when JIT-creating the customer.","anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"email":{"description":"Fallback lookup key + used when JIT-creating.","anyOf":[{"type":"string","maxLength":255,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"ttl_seconds":{"description":"Hand-off URL lifetime. Clamped to [60, 900] seconds; default 300.","type":"integer","minimum":60,"maximum":900}},"required":["store_id","external_id"]}},{"name":"delete_customer","title":"Delete a customer","description":"Delete a customer\n\nHard delete. Cascades through FKs:\n- `order_items.customer_id` is set to NULL (orders remain).\n- `customer_coin_balances` and `customer_coin_movements` are deleted.\n\nReturns `{ id, deleted: true }` on success. Idempotency is not\nrequired — DELETE is already idempotent at the HTTP level.\n\nRequires API key scope: customers:delete.","method":"DELETE","path":"/api/v1/customers/{id}","scopes":["customers:delete"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_brand_sub_brand","title":"Get brand and sub-brand metadata","description":"Get brand and sub-brand metadata\n\nReturns the calling API key's sub-brand together with its parent brand. Both path ids must match the key scope.\n\nRequires API key scope: brands:read.","method":"GET","path":"/api/v1/brands/{brandId}/sub-brands/{subBrandId}","scopes":["brands:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"brandId":{"type":"string","description":"Path parameter `brandId` (identifier or slug)."},"subBrandId":{"type":"string","description":"Path parameter `subBrandId` (identifier or slug)."}},"required":["brandId","subBrandId"]}},{"name":"get_catalog","title":"Fetch a catalog by id","description":"Fetch a catalog by id\n\nReturns the catalog if it is visible to the calling sub-brand (own or accepted brand-level). 404 otherwise — pending/rejected brand-level catalogs return 404 to prevent enumeration.\n\nRequires API key scope: catalogs:read.","method":"GET","path":"/api/v1/catalogs/{id}","scopes":["catalogs:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_customer","title":"Fetch a customer by id","description":"Fetch a customer by id\n\nReturns the customer if it belongs to the calling key's sub-brand.\n\n404 is intentionally returned for both unknown ids and ids in\nother sub-brands — distinguishing them would leak existence.\n\nRequires API key scope: customers:read.","method":"GET","path":"/api/v1/customers/{id}","scopes":["customers:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_customer_coins","title":"Get a customer's coin wallet snapshot","description":"Get a customer's coin wallet snapshot\n\nReturns the cached balance plus the 10 most recent movements\nfor the customer. Convenient for an activity-feed UI that\nshows running history without a separate paginated call.\n\nReturns `balance: 0` with an empty `recent_movements` array\nwhen the customer's wallet has never been touched.\n\nFor deeper history, a paginated `/coins/movements` endpoint\nships in a future release (v1.x).\n\nRequires API key scope: customers:read.","method":"GET","path":"/api/v1/customers/{id}/coins","scopes":["customers:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_health","title":"Liveness probe and key sanity check","description":"Liveness probe and key sanity check\n\nVerifies the calling key authenticates, has at least the `customers:read` scope, and counts against its rate-limit bucket. Returns the environment and sub-brand id encoded in the key so clients can confirm they are talking to the right tenant/mode.\n\nRequires API key scope: customers:read.","method":"GET","path":"/api/v1/health","scopes":["customers:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"get_order","title":"Fetch an order + its line items","description":"Fetch an order + its line items\n\nReturns the order header with its line items. Shipping details are also available via `GET /api/v1/orders/{id}/shipping` if you only need addresses + carrier info.\n\nRequires API key scope: orders:read.","method":"GET","path":"/api/v1/orders/{id}","scopes":["orders:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_order_reservation_request","title":"Get an asynchronous order reservation","description":"Get an asynchronous order reservation\n\nReturns the durable status of a reservation accepted with HTTP 202\nby `POST /api/v1/orders`. The id is scoped to the calling key's\nsub-brand. When status becomes `completed`, use `order_id` with\n`GET /api/v1/orders/{id}`. `failed` is terminal and includes a\nsafe failure code/message.\n\nRequires API key scope: orders:read.","method":"GET","path":"/api/v1/order-reservations/{id}","scopes":["orders:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_order_shipping","title":"Fetch per-item shipping details for an order","description":"Fetch per-item shipping details for an order\n\nReturns one entry per line item with the recipient + carrier\nfields, without the financial / billing fields. Convenient for\nlabel-printing flows and courier polling.\n\nRequires API key scope: orders:read.","method":"GET","path":"/api/v1/orders/{id}/shipping","scopes":["orders:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_product","title":"Fetch a product family + its variants","description":"Fetch a product family + its variants\n\nReturns the product family with its variants array. The path accepts\neither a legacy migrated product ID or the canonical UUIDv7.\n404 when the product is not visible to the calling sub-brand.\n\nThe top-level product fields include the final customer PVP\n(`price_cents`, `currency`, `price_country`) resolved through the\nprovider veil. Variant objects remain SKU/content-only in v1.\n\nRequires API key scope: products:read.","method":"GET","path":"/api/v1/products/{id}","scopes":["products:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"get_store","title":"Fetch a store by id or slug","description":"Fetch a store by id or slug\n\nThe `{id}` path parameter accepts either a UUID or the store's slug.\nSlugs are globally unique so disambiguation by shape is safe.\n\nReturns 404 if the store doesn't exist OR belongs to a different sub-brand.\n\nRequires API key scope: stores:read.","method":"GET","path":"/api/v1/stores/{id}","scopes":["stores:read"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}},{"name":"ingest_engine_event","title":"Deliver an event to the Engine","description":"Deliver an event to the Engine\n\nExecutes the event definition identified by `event_code` for the\nplayer referenced in `data` (resolved through the definition's\nconfigured player-id field, default `externalUserId`, matched\nagainst the customer's `external_id` within your sub-brand).\n\nA delivery that passes pre-flight always returns **200 with an\n`EngineEventResult`** — check `status`, don't assume `completed`:\n- `completed` — the points were applied to the wallet.\n- `skipped` — business rules declined: `outside_active_window`,\n  or a per-player velocity cap (`max_daily_reached` = daily limit,\n  `cooldown_active` = minimum interval between grants).\n- `failed` — the execution was recorded but did not apply:\n  `player_not_found`, `player_id_missing`, `insufficient_balance`,\n  or `internal_error` (transient).\n\n**Idempotency-Key header is REQUIRED** (this endpoint moves money).\nThe same value is also the BUSINESS idempotency key, so a\nredelivery through the webhook channel with the same id can never\ndouble-credit. A retry with the SAME key is normally answered by\nthe HTTP idempotency layer: the original response body verbatim\n(so `replayed` reads `false`) plus the `idempotent-replay: true`\nresponse header — key off the header. `replayed: true` in the body\nmarks a BUSINESS-layer replay (e.g. the same id already arrived\nvia webhook). Same key + a DIFFERENT body → 409\n`idempotency_conflict`.\n\nRetry semantics: a `failed` with `error_code: internal_error`\nreturns **500** and IS retryable with the same key (the pipeline\nreclaims the transient row); business `failed`/`skipped` are\nterminal and replay their frozen verdict.\n\nSending `points_override` requires the key to hold the\n`engine:overrides` scope AND the definition to be `external_bounded`\n(values outside the definition's min/max are rejected); otherwise the\ndelivery is rejected 400 `override_not_allowed`. A key with only\n`engine:events` keeps working for default-points and internal-only\nevents.\n\nPre-flight rejections (error ENVELOPE, no execution row):\n- 400 `override_not_allowed` — `points_override` sent without the\n  `engine:overrides` scope or to a definition that forbids it;\n  `invalid_points` (out of the definition's bounds); `payload_too_large`.\n- 403 — the Engine is not enabled for this brand.\n- 404 — unknown `event_code`.\n- 409 — the definition is not active.\n\nEvery delivery — completed, skipped, failed, replay, rejection —\nlands in the immutable Engine audit trail.\n\nRequires API key scope: engine:events.","method":"POST","path":"/api/v1/engine/events","scopes":["engine:events"],"idempotent":true,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"event_code":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,79}$","description":"Code of the event definition to execute (unique per sub-brand)."},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"The event payload, stored verbatim on the execution and audit records. The player is resolved from the definition's configured field (default `externalUserId`)."},"points_override":{"description":"Optional per-event points value (signed non-zero integer, |points| <= 1,000,000). Only honoured when the definition sets `allow_override`; sending it to a definition that forbids it is rejected 400 `override_not_allowed` (before any execution row is created).","anyOf":[{"type":"number"},{"type":"null"}]},"correlation_id":{"description":"Optional caller tracing id (echoed back). Generated when absent.","anyOf":[{"type":"string","maxLength":64},{"type":"null"}]},"idempotency_key":{"description":"Optional. Reuse the same value to safely retry this exact call without duplicating its effect. Auto-generated when omitted.","type":"string","minLength":1,"maxLength":64}},"required":["event_code","data"]}},{"name":"ingest_engine_webhook","title":"Deliver an event to the Engine via webhook","description":"Deliver an event to the Engine via webhook\n\nWebhook-style ingestion: POST the provider payload verbatim; the\nevent definition code travels in the path. Runs the exact same\npipeline as `POST /engine/events` (same ledger, same audit trail,\nsame velocity caps).\n\nRedeliveries dedupe on the payload field configured on the\ndefinition (`idempotencyKeyField`; default `event_id`, then `id`)\n— a stable provider id is strongly recommended. Ids longer than\n64 chars are hashed deterministically, so dedup always holds.\n\n**FAIL-CLOSED anti-replay:** a delivery with NO usable id (no\nmatching payload field AND no `Idempotency-Key` header) is\n**rejected 400 `missing_idempotency_key`** rather than executed —\nwithout a key an attacker could replay the same body to farm\npoints. Always send a stable event id.\n\nResponds 200 with an `EngineEventResult` for every delivery that\npasses pre-flight — check `status` (`completed` / `skipped` for a\nrule or velocity cap / `failed`), don't assume `completed`.\n`replayed: true` marks a redelivery answered with the FROZEN\nfirst result. The same pre-flight rejections as `/engine/events`\napply (400 / 403 / 404 / 409; 500 for a transient `internal_error`\n— retry with the same id).\n\nRequires API key scope: engine:events.","method":"POST","path":"/api/v1/engine/webhooks/{code}","scopes":["engine:events"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"code":{"type":"string","description":"Path parameter `code` (identifier or slug)."}},"required":["code"]}},{"name":"list_catalogs","title":"List catalogs visible to the sub-brand","description":"List catalogs visible to the sub-brand\n\nReturns catalogs the calling sub-brand can use:\n1. Catalogs owned exclusively by this sub-brand (`scope: \"sub_brand\"`).\n2. Brand-level catalogs the sub-brand has ACCEPTED (`scope: \"brand\"`).\n\nPending or rejected brand-level catalogs are filtered out — clients\nsee them as if they didn't exist.\n\nRequires API key scope: catalogs:read.\n\nPaginated: pass `cursor` from the previous response's `page.next_cursor` to fetch more.","method":"GET","path":"/api/v1/catalogs","scopes":["catalogs:read"],"idempotent":false,"paginated":true,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"list_categories","title":"List product categories","description":"List product categories\n\nReturns the global canonical category list. The default is `active`; pass `status=inactive` to inspect disabled categories.\n\nRequires API key scope: categories:read.\n\nPaginated: pass `cursor` from the previous response's `page.next_cursor` to fetch more.","method":"GET","path":"/api/v1/categories","scopes":["categories:read"],"idempotent":false,"paginated":true,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","enum":["active","inactive"]}}}},{"name":"list_customer_coin_movements","title":"List a customer's full coin-movement history","description":"List a customer's full coin-movement history\n\nReturns every movement for the customer's wallet in reverse\nchronological order, cursor-paginated. Movements are append-\nonly — corrections appear as NEW rows with their own `delta`\nand `balance_after`, so the running history is reconstructable\nfrom this endpoint alone.\n\n404 if the customer doesn't exist or belongs to a different\nsub-brand.\n\nRequires API key scope: customers:read.\n\nPaginated: pass `cursor` from the previous response's `page.next_cursor` to fetch more.","method":"GET","path":"/api/v1/customers/{id}/coins/movements","scopes":["customers:read"],"idempotent":false,"paginated":true,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."},"limit":{"description":"Page size — default 25, max 100. Smaller pages are cheaper; large pages save round-trips when backfilling history.","type":"integer","minimum":1,"maximum":100},"cursor":{"description":"Opaque cursor from the previous page's `page.next_cursor`. Omit on the first page.","type":"string"}},"required":["id"]}},{"name":"list_customers","title":"List customers","description":"List customers\n\nReturns the calling key's sub-brand customers in reverse\nchronological order. Pagination is cursor-based — call again with\n`?cursor=<page.next_cursor>` until `page.has_more` is false.\n\nAll filters combine with AND. Filter by status, segment, country,\ncreation/registration date windows, coin-balance range and\nlifetime order-count range — the same targeting the bulk-coins\nendpoint uses.\n\nRequires API key scope: customers:read.\n\nPaginated: pass `cursor` from the previous response's `page.next_cursor` to fetch more.","method":"GET","path":"/api/v1/customers","scopes":["customers:read"],"idempotent":false,"paginated":true,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"q":{"description":"Substring match across `first_name`, `last_name`, `email`, `external_id`. Case-insensitive.","type":"string","minLength":1,"maxLength":200},"status":{"description":"Filter by moderation status.","type":"string","enum":["active","watch","restricted","banned"]},"segment_id":{"description":"Exact match on the casino-side segment/cohort tag.","type":"string","minLength":1,"maxLength":36},"country":{"description":"ISO-3166-1 alpha-2 country.","type":"string","minLength":2,"maxLength":2},"created_after":{"description":"Only customers created at/after this ISO-8601 UTC instant.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"created_before":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"registered_after":{"description":"Only customers whose casino `platform_registered_at` is at/after this instant.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"registered_before":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"min_coins":{"description":"Only customers whose coin balance is >= this value (0 default).","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"max_coins":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"min_orders":{"description":"Only customers with at least this many lifetime orders.","type":"integer","minimum":0,"maximum":9007199254740991},"max_orders":{"type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"description":"Default 25, max 100.","type":"integer","minimum":1,"maximum":100},"cursor":{"description":"Opaque cursor returned by the previous page's `page.next_cursor`. Omit on the first page.","type":"string"}}}},{"name":"list_orders","title":"List orders","description":"List orders\n\nCursor-paginated list of orders for the calling sub-brand. Newest\nfirst. Filter by status, store id, or a creation-time window.\n\nBilling-layer fields (batch references, payment_reference, internal\nTPP sync state) are NOT exposed — those require a dedicated scope\nthat ships in a future release.\n\nRequires API key scope: orders:read.\n\nPaginated: pass `cursor` from the previous response's `page.next_cursor` to fetch more.","method":"GET","path":"/api/v1/orders","scopes":["orders:read"],"idempotent":false,"paginated":true,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","enum":["pending","processing","shipped","completed","failed","cancelled"]},"store_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"customer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"q":{"type":"string","minLength":1},"created_from":{"description":"Inclusive lower bound on `created_at` (ISO-8601).","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"created_to":{"description":"Inclusive upper bound on `created_at` (ISO-8601).","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"limit":{"type":"integer","minimum":1,"maximum":100},"cursor":{"type":"string"}}}},{"name":"list_stores","title":"List storefronts","description":"List storefronts\n\nCursor-paginated list of storefronts belonging to the calling key's sub-brand. Filter by status or name/slug substring.\n\nRequires API key scope: stores:read.\n\nPaginated: pass `cursor` from the previous response's `page.next_cursor` to fetch more.","method":"GET","path":"/api/v1/stores","scopes":["stores:read"],"idempotent":false,"paginated":true,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"q":{"type":"string","minLength":1,"maxLength":200},"status":{"type":"string","enum":["draft","active","inactive"]},"limit":{"type":"integer","minimum":1,"maximum":100},"cursor":{"type":"string"}}}},{"name":"reserve_order","title":"Reserve a new order","description":"Reserve a new order\n\nCreates an order in the calling key's sub-brand from one or more\ncatalog listings. Each line item must reference an existing\n`provider_listing_markets` row available in the sub-brand's country\nAND an existing customer of the same sub-brand.\n\n**Idempotency-Key is required.** Replays with the same key + body\nreturn the cached response (the order WAS created by the first\nexecution). Replays with a different body return 409\n`idempotency_conflict`.\n\n**Pricing.** Each line's `unit_price_cents` is snapshotted from\n`provider_listing_markets.price` at creation. Margin precedence is\napplied internally: product override → brand default → provider\ndefault. The brand-discount-pct is also snapshotted so future\nconfig changes don't move historical totals.\n\n**Billing.**\n- `prepaid_balance` brands: the saldo is reserved atomically. If\n  insufficient and `allow_negative_balance=false`, the order is\n  created with `billing_status='blocked_balance'` — fulfilment\n  cannot proceed until the brand tops up. Watch the field on the\n  response.\n- `monthly_billing` brands: the order is attached to the current\n  open billing batch (created lazily for the period).\n- `pay_per_order` brands: the order is born with\n  `payment_status='pending_payment'` — fulfilment is gated until\n  the admin marks it paid in the backoffice.\n\n**Country gate.** All listings MUST match the sub-brand's country.\nA mixed-country payload returns 403 `forbidden`.\n\nPhase 3b ships catalog items only. Manual items (price negotiated\nclient-side, no `provider_listing_markets` row) land in a future\nrelease.\n\n**Transient-failure fallback.** A validated request that encounters\nan explicitly retryable database/infrastructure failure is durably\naccepted with HTTP 202. The response carries a\n`reservation_request_id`; poll\n`GET /api/v1/order-reservations/{id}` for completion. Validation,\nauthorization, catalog, stock, billing, and all unknown/non-retryable\nerrors remain ordinary 4xx/5xx errors and are never queued.\n\nRequires API key scope: orders:create.","method":"POST","path":"/api/v1/orders","scopes":["orders:create"],"idempotent":true,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"items":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"listing_market_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"UUID of the `provider_listing_markets` row to purchase. Get one from `GET /products/{id}` (variant + market resolution lands in v1.x) or from your catalog tooling."},"quantity":{"type":"integer","minimum":1,"maximum":1000,"description":"Units to purchase. Brand-level cart limit caps the total across items."},"customer_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Customer who receives this line. MUST belong to the calling sub-brand. The customer's address snapshot fills the line's `shipping_address` and `recipient_*` fields."}},"required":["listing_market_id","quantity","customer_id"]},"description":"Line items. At least one; brand-level cart limit applies."},"store_id":{"description":"Optional store the order is attributed to. Analytics-only — orders are scoped to the sub-brand regardless of this value.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"currency":{"description":"ISO-4217 currency override. Defaults to the sub-brand's configured default (typically EUR).","type":"string","minLength":3,"maxLength":3},"idempotency_key":{"description":"Optional. Reuse the same value to safely retry this exact call without duplicating its effect. Auto-generated when omitted.","type":"string","minLength":1,"maxLength":64}},"required":["items"]}},{"name":"search_products","title":"Search products visible to the sub-brand","description":"Search products visible to the sub-brand\n\nReturns product families reachable via any catalog the sub-brand owns\nor has accepted. Product IDs are canonical UUIDv7 values from\n`product_families.id`; legacy product IDs are kept only in\nthe auxiliary resolution table.\n\nEach product includes `price_cents` / `currency` / `price_country`:\nthe final customer PVP resolved server-side from active listings in\nthe sub-brand's markets. The raw provider cost is never exposed.\n\nRequires API key scope: products:read.\n\nPaginated: pass `cursor` from the previous response's `page.next_cursor` to fetch more.","method":"GET","path":"/api/v1/products","scopes":["products:read"],"idempotent":false,"paginated":true,"annotations":{"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"q":{"description":"Substring match across `name` and `slug` (case-insensitive).","type":"string","minLength":1,"maxLength":200},"category_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"catalog_id":{"description":"Restrict the search to a specific catalog. The catalog must already be visible to the calling sub-brand.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"type":"integer","minimum":1,"maximum":100},"cursor":{"type":"string"}}}},{"name":"update_customer","title":"Partial-update a customer","description":"Partial-update a customer\n\nUpdates the fields present in the body. Omitted fields are NOT\nchanged. To unset a nullable field, send it explicitly as `null`\n(this is PATCH semantics, NOT JSON-merge-patch).\n\n`status` transitions stamp `status_changed_at` automatically.\n\nRequires API key scope: customers:update.","method":"PATCH","path":"/api/v1/customers/{id}","scopes":["customers:update"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."},"external_id":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"segment_id":{"anyOf":[{"type":"string","maxLength":36},{"type":"null"}]},"first_name":{"type":"string","minLength":1,"maxLength":120},"last_name":{"type":"string","minLength":1,"maxLength":120},"nickname":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"email":{"anyOf":[{"type":"string","maxLength":255,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"address_line":{"anyOf":[{"type":"string","maxLength":240},{"type":"null"}]},"postal_code":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"zone":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"locality":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"city":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"state":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"country":{"anyOf":[{"type":"string","minLength":2,"maxLength":2},{"type":"null"}]},"preferred_lang":{"anyOf":[{"type":"string","enum":["en","es","pt","it","fr"]},{"type":"null"}]},"platform_registered_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"status":{"type":"string","enum":["active","watch","restricted","banned"]},"status_note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["id"]}},{"name":"update_order_shipping","title":"Add or update an order's shipping address(es)","description":"Add or update an order's shipping address(es)\n\nSet the delivery address for an order. Two modes:\n\n**Grouped** — send `shipping_address` (+ optional recipient\nfields) at the top level and the SAME address is written to\nevery line item. The order's `shipping_mode` becomes `grouped`.\n\n```json\n{ \"shipping_address\": \"Calle Mayor 1\\n28013 Madrid\\nES\",\n  \"recipient_name\": \"Ana Pérez\" }\n```\n\n**Split** — send an `items` array, one entry per line item\n(addressed independently by `item_id`). The order's\n`shipping_mode` becomes `split`. Every `item_id` must belong\nto the order.\n\n```json\n{ \"items\": [\n  { \"item_id\": \"01H…\", \"shipping_address\": \"…\", \"recipient_name\": \"Ana\" },\n  { \"item_id\": \"01H…\", \"shipping_address\": \"…\", \"recipient_name\": \"Luis\" }\n] }\n```\n\nOnly recipient + address fields are touched — carrier and\ntracking stay fulfilment-side. Rejected with 409 once the\norder is `completed` or `cancelled`. Returns the full per-item\nshipping projection after the update.\n\nRequires API key scope: orders:update.","method":"PATCH","path":"/api/v1/orders/{id}/shipping","scopes":["orders:update"],"idempotent":false,"paginated":false,"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":true},"input_schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"Path parameter `id` (identifier or slug)."}},"required":["id"]}}]}