{
  "info": {
    "_postman_id": "0199bd1e-7a4c-7f00-9d5e-4e2f6b0a91c7",
    "name": "WaaS API 0.8.0",
    "description": "Generated from https://docs.walletd.io/openapi.yaml by scripts/build-collection.mjs. Set baseUrl and apiKey. Before each keyed operation, fill its idempotency_* collection variable with a saved operation key. Reuse the same key and parameters for retries; change the key only for a new operation. Postman skips requests with missing or invalid keys. Other importers may discard scripts: manually set the empty Idempotency-Key header before sending.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://apigw.walletd.io",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "sk_sandbox_...",
      "type": "string"
    },
    {
      "key": "idempotency_post__v1_transfers",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_topups",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_payments",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_payments_paymentId_capture",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_refunds",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_rewards_convert",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_subscriptions",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_clients_clientId_marketing_fund",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_clients_clientId_payouts",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_orders",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_explore_offerings_offeringId_purchase",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    },
    {
      "key": "idempotency_post__v1_explore_offerings_offeringId_subscribe",
      "value": "",
      "type": "string",
      "description": "Required saved operation key. Reuse with identical parameters on retry; change only for a new operation."
    }
  ],
  "item": [
    {
      "name": "Users and balances",
      "description": "Wallet users, their balances per account purpose, and their transaction history.",
      "item": [
        {
          "name": "Resolve the calling user token to its wallet user",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "Resolves the calling user token to its wallet user. Only a user token has a self: an API key or a staff session is refused `user_token_required` (403), and a token whose subject has no wallet user in this tenant gives `user_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Search and page the tenant's users (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users"
              ],
              "query": [
                {
                  "key": "query",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Search and page the tenant's users. Requires `users:read` and a machine or staff principal — a user token is refused `forbidden` (403) because listing everyone is not a self-read. `query` matches the user's identifying fields. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Create or attach a wallet user",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_id\": \"user-42\",\n  \"kind\": \"consumer\",\n  \"handle\": \"ada\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users"
              ]
            },
            "description": "Creates a wallet user, or attaches one to an identity you already have. Requires `users:write`. `external_id` is your own identifier and is what makes this call safe to repeat: a second create with the same `external_id` is refused `user_exists` (409) rather than opening a second wallet. A missing body or a field outside its bounds gives `invalid_request` (400)."
          },
          "response": []
        },
        {
          "name": "Fetch a wallet user",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users/{userId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                ":userId"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "<userId>"
                }
              ]
            },
            "description": "Fetches one wallet user. Requires `users:read`. An API key may read any user in the tenant; a user token may read only itself and is refused `forbidden` (403) for anyone else. An unknown id gives `user_not_found` (404). A staff read is recorded in the sensitive-read access log."
          },
          "response": []
        },
        {
          "name": "List a user's balances per account purpose",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users/{userId}/balances",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                ":userId",
                "balances"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "<userId>"
                }
              ]
            },
            "description": "One row per account purpose and commodity, each with `balance`, `held` and `available`, as the tenant's ledger of record reports them; held is money reserved by an authorization that has not been captured. Requires `balances:read`. An API key may read any user, a user token only itself (`forbidden`, 403); an unknown id gives `user_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "A user's ledger history across cash and points",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users/{userId}/transactions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                ":userId",
                "transactions"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "<userId>"
                }
              ]
            },
            "description": "The user's own ledger entries across cash and points, newest first, each with the transaction it belongs to, its direction and its commodity. Requires `balances:read`; a user token may read only itself (`forbidden`, 403) and an unknown id gives `user_not_found` (404). A staff read is recorded in the sensitive-read access log. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Register the calling user's device for push notifications",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/devices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "devices"
              ]
            },
            "description": "Records a Firebase Cloud Messaging token so money events can reach the caller's device. The token is bound to the wallet user behind the credential, and registering the same device again rebinds it, so this is safe to call on every app start. Answers 204 with no body. Refuses `not_a_user` (403) when the credential does not resolve to a wallet user in this tenant, `invalid_request` (400) when `token` is empty or `platform` is not `android` or `ios`, and `push_disabled` (501) on a deployment with no push service configured."
          },
          "response": []
        },
        {
          "name": "Set a business user's credit line (audited; API keys only)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/users/{userId}/credit_limit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                ":userId",
                "credit_limit"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "<userId>"
                }
              ]
            },
            "description": "Sets a business user's credit line, the floor below zero its cash account may go. Requires `credit:write` and an API key or staff session: a user token is refused `forbidden` (403) even with the scope, because a customer does not set their own limit. Refuses `user_not_found` (404), `user_not_business` (422) for a consumer wallet, and `credit_limit_below_balance` (422) when the new limit is under what the user has already drawn. Every change lands in the audit log."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Top-ups",
      "description": "Money in, through a payment gateway. The wallet is credited only on verified confirmation.",
      "item": [
        {
          "name": "The configured top-up gateways, for client UIs picking a rail",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/topup_methods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "topup_methods"
              ]
            },
            "description": "The top-up gateways this deployment has configured, each with the flow a client UI must drive (`client_secret` or `redirect`) and the currencies it accepts. Requires `topups:read`. Adding a gateway to the deployment adds it here; nothing else has to change."
          },
          "response": []
        },
        {
          "name": "Start a top-up through a payment gateway",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"user_id\": \"0191c2d4-8f3a-7c51-9b2e-4a6f8d310e27\",\n  \"amount\": 5000,\n  \"gateway\": \"stripe\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/topups",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "topups"
              ]
            },
            "description": "Starts a top-up and returns an intent. **It does not credit the wallet.** The balance moves only when the processor's verified confirmation arrives, over the gateway webhook or a later `refresh`, so a 201 here means \"the user may now pay\", not \"the user has paid\". Follow `next_action` to complete the payment. Requires `topups:write` and an `Idempotency-Key`; an API key may top up any user, a user token only itself (`forbidden`, 403). Refuses `user_not_found` (404), `tier_limit_exceeded` (422), and `idempotency_key_reuse` (409) when the key comes back with a different body."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_topups\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_topups to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Fetch a top-up intent",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/topups/{topupId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "topups",
                ":topupId"
              ],
              "variable": [
                {
                  "key": "topupId",
                  "value": "<topupId>"
                }
              ]
            },
            "description": "Fetches a top-up intent and its current status. This is a read of what walletd already knows; it does not ask the gateway — use `refresh` for that. It never returns `next_action` or `client_secret`: those exist only in the response that created the intent, so capture them there rather than expecting to read them back. Requires `topups:read`; a user token may read only its own intents (`forbidden`, 403) and an unknown id gives `topup_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Ask the gateway for a pending top-up's current outcome",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/topups/{topupId}/refresh",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "topups",
                ":topupId",
                "refresh"
              ],
              "variable": [
                {
                  "key": "topupId",
                  "value": "<topupId>"
                }
              ]
            },
            "description": "Reconciles one intent on demand: the gateway is asked what it thinks, and a terminal answer is applied through the same verified-confirmation path a webhook uses, so a credit can never happen twice. Use it when a webhook was missed or delayed; a settled intent is returned unchanged."
          },
          "response": []
        },
        {
          "name": "Inbound payment-processor callback (processors only, never called by an integrator)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/gateways/{gateway}/webhook",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "gateways",
                ":gateway",
                "webhook"
              ],
              "variable": [
                {
                  "key": "gateway",
                  "value": "<gateway>"
                }
              ]
            },
            "description": "**Inbound.** This is where the payment processor tells WalletD that a top-up succeeded or failed; an integrator never calls it. It is documented because it is the path by which a top-up's money actually lands, and because a partner operating its own gateway account has to point the processor at it. There is no bearer token: the processor's own signature over the raw body is the authentication, which is why the body must reach walletd unmodified. A verified terminal outcome is applied through the same confirmation path a manual refresh uses, and an intent already in a terminal state is left untouched, so a replayed callback cannot credit twice. A succeeded outcome whose reported amount or currency disagrees with the intent is parked as `amount_mismatch` and never credited; resolving that is an operator decision. Failures answer in problem+json like every other operation: 404 `gateway_not_found` for a gateway this deployment has not configured or that has no webhook, 400 `invalid_request` for a body that cannot be read, 401 `webhook_verification_failed` for a signature that does not verify, and 500 `internal_error` when the confirmation itself fails — which the processor should retry."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Transfers",
      "description": "Peer-to-peer movement inside the tenant loop.",
      "item": [
        {
          "name": "P2P transfer between users in the tenant loop",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"from_user\": \"0191c2d5-2b18-7e63-8f4a-05c7b9d1e832\",\n  \"to_alias\": {\n    \"handle\": \"ada\"\n  },\n  \"amount\": 1500\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transfers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transfers"
              ]
            },
            "description": "Moves cash between two users inside the same tenant loop, in one balanced posting or not at all. Requires `transfers:write` and an `Idempotency-Key`. Name the recipient either by `to_user` or by a single `to_alias` (phone, email or handle). The sender must be the caller when a user token is used (`forbidden`, 403). Refuses `self_transfer` (400), `sender_not_found` or `recipient_not_found` (404), `insufficient_funds` (422), `tier_limit_exceeded` (422) when the sender's tier cap is reached, and `idempotency_key_reuse` (409) when the key comes back with a different body; a replay with the same body returns the original transfer."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_transfers\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_transfers to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Payments and refunds",
      "description": "Paying a merchant instantly or as an authorization hold, then capturing, voiding or refunding it.",
      "item": [
        {
          "name": "Pay a merchant, instantly or as an authorization hold",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"payer_user_id\": \"0191c2d4-8f3a-7c51-9b2e-4a6f8d310e27\",\n  \"merchant_id\": \"0191c2d6-1a44-7b09-8e73-2c5f9a08d641\",\n  \"amount\": 1840,\n  \"mode\": \"instant\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/payments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments"
              ]
            },
            "description": "Charges a wallet user and settles the merchant, in one balanced posting or not at all. `mode: instant` moves the money now; `mode: authorize` holds it on the payer's account until a capture or a void, and the hold expires by itself after `hold_ttl_seconds` (900 by default). Requires `payments:write` and an `Idempotency-Key`; an API key may pay for any user, a user token only for itself (`forbidden`, 403). Refuses `payer_not_found` or `merchant_not_found` (404), `insufficient_funds` (422), and `idempotency_key_reuse` (409) when the key comes back with a different body; a replay with the same body returns the original payment."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_payments\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_payments to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Fetch a payment",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/{paymentId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<paymentId>"
                }
              ]
            },
            "description": "Fetches one payment with its captured and refunded totals, its fee, and any marketplace commission. Requires `payments:read`; a user token may read only payments it made (`forbidden`, 403) and an unknown id gives `payment_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Capture an authorized payment, optionally partially",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 58000\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/payments/{paymentId}/capture",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "capture"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<paymentId>"
                }
              ]
            },
            "description": "Captures an authorized payment. `amount` defaults to the full authorization; capturing less releases the remainder of the hold back to the payer in the same posting. Requires `payments:write`, an `Idempotency-Key`, and a merchant-side principal: a user token is refused `insufficient_scope` (403) even when it carries the scope, so no one can capture a stranger's hold by its id. Refuses `payment_not_found` (404), `invalid_state` (409) when the payment is not awaiting capture, and `capture_exceeds_authorized` (422)."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_payments_paymentId_capture\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_payments_paymentId_capture to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Release an authorized payment without capturing",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/{paymentId}/void",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "void"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<paymentId>"
                }
              ]
            },
            "description": "Releases an authorized payment without taking any money; the whole hold returns to the payer and nothing settles. Requires `payments:write` and a merchant-side principal — a user token is refused `insufficient_scope` (403). Refuses `payment_not_found` (404) and `invalid_state` (409) when the payment is not awaiting capture."
          },
          "response": []
        },
        {
          "name": "Refund a captured payment back to the payer's wallet",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"payment_id\": \"0191c2da-7d55-7a80-9143-6be2f08c5d97\",\n  \"amount\": 950,\n  \"reason\": \"wrong item\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/refunds",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "refunds"
              ]
            },
            "description": "Returns captured money to the payer's wallet as a contra posting: the original transaction is never rewritten, so both movements stay in the history. Partial refunds may repeat, and the cumulative refunded amount can never exceed what was captured (`refund_exceeds_captured`, 422). Requires `refunds:write` and an `Idempotency-Key`. Refuses `payment_not_found` (404), `invalid_state` (409) for a payment that was never captured, `insufficient_funds` (422) when the funding side cannot cover it, and `idempotency_key_reuse` (409) when the key comes back with a different body."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_refunds\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_refunds to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Subscriptions",
      "description": "Recurring charge mandates, their lifecycle, and the dunning that follows a failed cycle.",
      "item": [
        {
          "name": "Page the tenant's subscriptions (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/subscriptions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "subscriptions"
              ],
              "query": [
                {
                  "key": "user_id",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "The tenant's subscriptions, optionally narrowed by `user_id` and `status`. Requires `subscriptions:read` and a machine or staff principal; a user token is refused `forbidden` (403). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Create a recurring charge mandate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"user_id\": \"0191c2d4-8f3a-7c51-9b2e-4a6f8d310e27\",\n  \"merchant_id\": \"0191c2d6-1a44-7b09-8e73-2c5f9a08d641\",\n  \"plan_name\": \"FoodPass\",\n  \"amount\": 999,\n  \"interval\": \"monthly\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/subscriptions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "subscriptions"
              ]
            },
            "description": "Records a recurring charge mandate. Creating one moves no money: the first charge runs at `start_at` (now when omitted) and the scheduler charges once per interval after that, retrying a failed period before the subscription goes past due. Requires `subscriptions:write` and an `Idempotency-Key`; an API key may subscribe any user, a user token only itself (`forbidden`, 403). Refuses `user_not_found` or `merchant_not_found` (404), `invalid_request` (400), and `idempotency_key_reuse` (409) when the key comes back with a different body."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_subscriptions\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_subscriptions to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Fetch a subscription",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/subscriptions/{subscriptionId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "subscriptions",
                ":subscriptionId"
              ],
              "variable": [
                {
                  "key": "subscriptionId",
                  "value": "<subscriptionId>"
                }
              ]
            },
            "description": "Fetches one subscription with its status, next charge time and retry count. Requires `subscriptions:read`; a user token may read only its own (`forbidden`, 403) and an unknown id gives `subscription_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Pause charging",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/subscriptions/{subscriptionId}/pause",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "subscriptions",
                ":subscriptionId",
                "pause"
              ],
              "variable": [
                {
                  "key": "subscriptionId",
                  "value": "<subscriptionId>"
                }
              ]
            },
            "description": "Stops charging without ending the mandate; nothing is billed while it is paused. Requires `subscriptions:write`, and a user token may pause only its own subscription (`forbidden`, 403). Refuses `subscription_not_found` (404) and `invalid_state` (409) when the subscription is not in a state that can be paused. Audited."
          },
          "response": []
        },
        {
          "name": "Resume a paused subscription; the period re-anchors at the resume time",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/subscriptions/{subscriptionId}/resume",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "subscriptions",
                ":subscriptionId",
                "resume"
              ],
              "variable": [
                {
                  "key": "subscriptionId",
                  "value": "<subscriptionId>"
                }
              ]
            },
            "description": "Restarts a paused subscription. The period re-anchors at the resume time, so the subscriber is not billed for the pause and the next charge is a full interval away. Requires `subscriptions:write`, and a user token may resume only its own (`forbidden`, 403). Refuses `subscription_not_found` (404) and `invalid_state` (409). Audited."
          },
          "response": []
        },
        {
          "name": "Cancel a subscription permanently",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/subscriptions/{subscriptionId}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "subscriptions",
                ":subscriptionId",
                "cancel"
              ],
              "variable": [
                {
                  "key": "subscriptionId",
                  "value": "<subscriptionId>"
                }
              ]
            },
            "description": "Ends the mandate permanently — a cancelled subscription cannot be resumed, and a new one has to be created instead. Requires `subscriptions:write`, and a user token may cancel only its own (`forbidden`, 403). Refuses `subscription_not_found` (404) and `invalid_state` (409). Audited."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Rewards and loyalty",
      "description": "Reward rules, the grants they produce, and converting points back into wallet cash.",
      "item": [
        {
          "name": "Disable a reward rule; existing grants stand",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/reward_rules/{ruleId}/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reward_rules",
                ":ruleId",
                "disable"
              ],
              "variable": [
                {
                  "key": "ruleId",
                  "value": "<ruleId>"
                }
              ]
            },
            "description": "Stops a rule being evaluated from now on. Grants it already made stand, and the rule stays in the listing so a past grant can still be explained. Requires `rewards:manage`; an unknown rule gives `rule_not_found` (404). Audited."
          },
          "response": []
        },
        {
          "name": "Create a reward rule (cashback, accrual, or conversion)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/reward_rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reward_rules"
              ]
            },
            "description": "Creates a cashback, accrual or conversion rule, scoped to merchants, categories or one offering, and capped per transaction and per user per day. Requires `rewards:manage`. Rules are evaluated as of the instant a payment was captured, not when the reward job runs, so a rule created after a capture never earns on it. `invalid_request` (400) covers a missing body, an unknown kind and parameters the rule engine rejects."
          },
          "response": []
        },
        {
          "name": "List reward rules",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/reward_rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reward_rules"
              ]
            },
            "description": "Every reward rule the tenant has, including disabled ones, oldest first, each with its parameters, scope and caps. Requires `rewards:manage`."
          },
          "response": []
        },
        {
          "name": "Convert points to wallet cash at the tenant rate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"user_id\": \"0191c2d4-8f3a-7c51-9b2e-4a6f8d310e27\",\n  \"points\": 650\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/rewards/convert",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "rewards",
                "convert"
              ]
            },
            "description": "Burns loyalty points and credits the user's wallet cash at the tenant's active conversion rule, in one balanced posting. Requires `rewards:convert` and an `Idempotency-Key`. An API key may convert for any user; a user token only for itself (`forbidden`, 403). Refuses `no_conversion_rule` (422) when the tenant has no active conversion rule, `insufficient_points` (422), and `idempotency_key_reuse` (409) when the same key comes back with a different body — a replay with the same body returns the original conversion."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_rewards_convert\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_rewards_convert to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "List a user's reward grants, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/users/{userId}/rewards",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                ":userId",
                "rewards"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "<userId>"
                }
              ]
            },
            "description": "A user's reward grants, newest first, each naming the rule that made it and the transaction it credited. Requires `rewards:read`; a user token may read only itself (`forbidden`, 403) and an unknown id gives `user_not_found` (404). `limit` is clamped to 1-100."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Limits and tiers",
      "description": "Per-tier caps on money movement and the tier a user is assigned to.",
      "item": [
        {
          "name": "The tenant's configured tier caps (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tier_limits",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tier_limits"
              ]
            },
            "description": "The tenant's configured caps per tier: maximum balance, top-up per day and peer-to-peer per day. Requires `limits:manage` and a machine or staff principal; a user token is refused `admin_only` (403) and a principal without the scope `insufficient_scope` (403)."
          },
          "response": []
        },
        {
          "name": "Upsert one tier's caps (audited; admin)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/tier_limits",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tier_limits"
              ]
            },
            "description": "Creates or replaces one tier's caps. Requires `limits:manage` and a machine or staff principal — a user token is refused `admin_only` (403). A cap outside its bounds gives `invalid_request` (400). The change is audited, and it applies to the next transaction, never retroactively."
          },
          "response": []
        },
        {
          "name": "Assign a user's limit tier (audited; admin)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/users/{userId}/tier",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "users",
                ":userId",
                "tier"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "<userId>"
                }
              ]
            },
            "description": "Assigns a user to a limit tier and answers with the tier it replaced. Requires `limits:manage` and a machine or staff principal; a user token is refused `admin_only` (403). An unknown tier gives `invalid_request` (400) and an unknown user `user_not_found` (404). Audited."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Merchants",
      "description": "Merchants and their settlement accounts.",
      "item": [
        {
          "name": "Register a merchant with its settlement account",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/merchants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "merchants"
              ]
            },
            "description": "Registers a merchant and its settlement account so it can be paid. Requires `merchants:write`. `external_id` is yours: a second create with the same one is refused `merchant_exists` (409) rather than opening a second settlement account. A missing body or a field outside its bounds gives `invalid_request` (400)."
          },
          "response": []
        },
        {
          "name": "Fetch a merchant",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/merchants/{merchantId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "merchants",
                ":merchantId"
              ],
              "variable": [
                {
                  "key": "merchantId",
                  "value": "<merchantId>"
                }
              ]
            },
            "description": "Fetches one merchant. Requires `merchants:read`; an unknown id gives `merchant_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "List settlement account entries",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/merchants/{merchantId}/statement",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "merchants",
                ":merchantId",
                "statement"
              ],
              "query": [
                {
                  "key": "since",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "merchantId",
                  "value": "<merchantId>"
                }
              ]
            },
            "description": "The merchant's settlement entries, newest first. Settlement is sharded across several accounts, so the statement is the merchant's owner history filtered to the settlement purpose rather than one account's rows; no single account is the statement. `since` bounds it and `limit` is clamped to 1-100. Requires `merchants:read`; an unknown id gives `merchant_not_found` (404). A merchant that has never moved money returns an empty list, which is the honest answer rather than an error."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Catalog",
      "description": "Offerings, variants, categories and stock, owned by the ecosystem client that sells them.",
      "item": [
        {
          "name": "Upload one image and get the URL to store on a profile or offering",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/media",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "media"
              ]
            },
            "description": "Stores one image in the deployment's object store and answers with its public URL, which you then pass as `logo_url` or `image_url` on a separate profile or offering write; uploading alone changes nothing a shopper can see. The body is `multipart/form-data` with a single `file` part, capped at 5 MiB. The content type is sniffed from the bytes rather than trusted from the part header, so a mislabelled upload is still classified correctly or rejected. Any authenticated principal may upload. Refuses `invalid_request` (400) when there is no `file` part, `unsupported_type` (415) for anything that is not PNG, JPEG, WebP or GIF, and `media_disabled` (501) on a deployment with no object store configured."
          },
          "response": []
        },
        {
          "name": "Self-serve ecosystem client onboarding (org + merchant + profile)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients"
              ]
            },
            "description": "Self-serve onboarding: creates the identity-provider organization, the merchant with its settlement account and the public profile in one call. Requires `clients:onboard` and a consumer token from the identity provider — an API key or staff session is refused `consumer_token_required` (403). Calling it again from the same subject returns the existing client with 200 instead of 201, so a retried signup does not make a second shop. Refuses `invalid_request` (400), and `org_provisioning_unavailable` (503) when the identity provider cannot be reached, which is worth retrying."
          },
          "response": []
        },
        {
          "name": "The clients the caller may manage",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "A consumer gets the clients its organization memberships cover, which is how a founder finds their own client after onboarding. Staff holding clients:manage get the loop's clients instead, optionally by status."
          },
          "response": []
        },
        {
          "name": "What this client sold, and what it kept",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/sales",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "sales"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The client's own trading history: a summary (sales, gross, fees, marketplace commission, refunded and net, plus subscriber count) followed by a page of individual sales, newest first by `created_at`, ties broken by the payment id so a page boundary never skips or repeats a sale. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Fetch a client (members and loop staff)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Fetches one client with its profile, status, commission override and the merchant it settles to. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Update the client's public identity",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "profile"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Replaces the client's public identity — display name, category, description and logo — as Explore shows it. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). A field outside its bounds, or a logo URL the service will not accept, gives `invalid_request` (400)."
          },
          "response": []
        },
        {
          "name": "The client's own offerings, one page at a time",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "category_id",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "q",
                  "value": "",
                  "description": "Matches title, SKU or barcode",
                  "disabled": true
                },
                {
                  "key": "low_stock",
                  "value": "",
                  "description": "Only items with a variant at or under its low-stock threshold",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The client's own offerings in every status, with the merchant view of each variant: cost, markup, pricing mode and stock counts, none of which Explore ever shows a shopper. Filter by `status`, `category_id`, free-text `q` and `low_stock`. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Answers with `next_cursor`; `limit` is clamped to 1-100."
          },
          "response": []
        },
        {
          "name": "Create a draft offering",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Creates an offering as a draft; nothing reaches Explore until it is published. `kind: item` is a product, which may carry options and variants and may be priced from cost plus a markup; `kind: plan` is a subscription, which has an interval and a single price. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400) for a shape the catalog rejects (a plan with variants, a product with options but no variants, more than the permitted options or variants) and `conflict` (409) for a variant SKU or option combination another variant already uses."
          },
          "response": []
        },
        {
          "name": "One of the client's own offerings, any status, with its live variants",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "One of the client's own offerings in any status, with its live variants and the merchant-only pricing fields. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). An unknown offering gives `offering_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Edit an offering",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "A draft may change anything. A published item may be repriced, and the change is recorded in its price history. A published plan may only change how it presents itself, because subscribers are already being charged its price. An archived offering cannot be edited at all."
          },
          "response": []
        },
        {
          "name": "Publish a draft offering to discovery",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}/publish",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId",
                "publish"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "Moves a draft into discovery. This is the only client action that puts something in front of the loop's users, so it is the one that waits for approval: a client the platform has not made active is refused `client_not_approved` (409), while drafting, pricing and profile edits all work while it is pending. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `offering_not_found` (404) and `invalid_transition` (409)."
          },
          "response": []
        },
        {
          "name": "Archive an offering (draft or published)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}/archive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId",
                "archive"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "Retires an offering, draft or published: it leaves discovery and can no longer be edited or bought. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `offering_not_found` (404) and `invalid_transition` (409) when the offering is already archived."
          },
          "response": []
        },
        {
          "name": "Add a variant to an item",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}/variants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId",
                "variants"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "Adds a variant to a product, with its own option values, price or cost-plus markup, SKU and optional inventory tracking. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400) when the variant does not carry one value per product option or the product is already at its variant ceiling, `not_found` (404), and `conflict` (409) when another variant already uses the SKU or that combination of options."
          },
          "response": []
        },
        {
          "name": "Edit a variant; a price change is recorded in history",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}/variants/{variantId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId",
                "variants",
                ":variantId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                },
                {
                  "key": "variantId",
                  "value": "<variantId>"
                }
              ]
            },
            "description": "Edits one variant. A price change is written to the client's price history with the actor that made it, so a shopper's disputed price can be explained later. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400), `not_found` (404), `invalid_transition` (409) for an archived variant, and `conflict` (409) for a SKU or option combination another variant already uses."
          },
          "response": []
        },
        {
          "name": "Retire a variant (a product keeps at least one)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}/variants/{variantId}/archive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId",
                "variants",
                ":variantId",
                "archive"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                },
                {
                  "key": "variantId",
                  "value": "<variantId>"
                }
              ]
            },
            "description": "Retires one variant. A product keeps at least one live variant, so archiving the last one is refused `invalid_request` (400) — archive the product instead. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_transition` (409) when it is already archived and `conflict` (409) while open orders still hold units of it in reserve."
          },
          "response": []
        },
        {
          "name": "Change a tracked variant's on-hand count, with a reason",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/offerings/{offeringId}/variants/{variantId}/stock",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "offerings",
                ":offeringId",
                "variants",
                ":variantId",
                "stock"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                },
                {
                  "key": "variantId",
                  "value": "<variantId>"
                }
              ]
            },
            "description": "Changes a tracked variant's on-hand count, either by `delta` or to an absolute `set_to`, with a reason, and answers with the variant and the movement row it wrote. Reserved units are not touched: they belong to open orders. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400), `not_found` (404) and `invalid_transition` (409) for an archived variant."
          },
          "response": []
        },
        {
          "name": "The shop's stock log, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/inventory/movements",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "inventory",
                "movements"
              ],
              "query": [
                {
                  "key": "variant_id",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The shop's stock log, newest first: every reservation, sale, restock and manual adjustment with the on-hand and reserved counts it left behind and the actor who caused it. Optional `variant_id` narrows it. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "The shop's default markup and rounding",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/pricing",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "pricing"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The shop's default markup and rounding rule, which is what variants priced `cost_plus` derive their price from. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Set the shop's default markup and rounding, repricing cost_plus variants",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/pricing",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "pricing"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Sets the shop's default markup and rounding and immediately reprices every `cost_plus` variant; the response says how many were repriced and each change lands in the price history. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400) for a markup or rounding outside its bounds, or when a derived price would exceed the maximum amount."
          },
          "response": []
        },
        {
          "name": "Every price change in the shop, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/price_history",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "price_history"
              ],
              "query": [
                {
                  "key": "offering_id",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Every price change in the shop, newest first, with the old and new amount, what caused it (a manual edit or a repricing) and who did it. Optional `offering_id` narrows it. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "The shop's category tree, parents first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "categories"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The shop's category tree, parents before their children. Categories nest one level deep. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Create a category",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "categories"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Creates a category, optionally under a parent. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400) for a name outside its bounds, an unknown parent, a second level of nesting or a shop already at its category ceiling, and `conflict` (409) when the shop already has a category with that name."
          },
          "response": []
        },
        {
          "name": "Rename or move a category",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/categories/{categoryId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "categories",
                ":categoryId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "categoryId",
                  "value": "<categoryId>"
                }
              ]
            },
            "description": "Renames a category or moves it under a different parent. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400) when the move would make a category its own parent, nest deeper than one level, or move a category that has children, and `conflict` (409) for a name the shop already uses."
          },
          "response": []
        },
        {
          "name": "Delete an unused category",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/categories/{categoryId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "categories",
                ":categoryId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "categoryId",
                  "value": "<categoryId>"
                }
              ]
            },
            "description": "Deletes a category and answers 204. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). A category that still holds products or subcategories is refused `conflict` (409) with the counts in the detail; an unknown category gives `not_found` (404)."
          },
          "response": []
        },
        {
          "name": "The shop's imports, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/imports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "imports"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The shop's bulk imports, newest first, each with its status and its create, update, error and applied counts. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Upload a CSV or XLSX (and optionally a ZIP of images) for preview",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/imports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "imports"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Nothing lands until the import is committed. The file is validated in full in the background; poll the import until its status is ready, review the rows, then commit. Rows match existing products by SKU. Download the template for the column list."
          },
          "response": []
        },
        {
          "name": "One import and its counts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/imports/{importId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "imports",
                ":importId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "importId",
                  "value": "<importId>"
                }
              ]
            },
            "description": "One import with its current status and counts — poll this after starting or committing an import, both of which finish in the background. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). An unknown import gives `import_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "The preview, row by row",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/imports/{importId}/rows",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "imports",
                ":importId",
                "rows"
              ],
              "query": [
                {
                  "key": "action",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "after",
                  "value": "",
                  "description": "Last seen row_no from the previous page",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "importId",
                  "value": "<importId>"
                }
              ]
            },
            "description": "The parsed preview, row by row: what each row would create or update, the image it resolved, and the validation errors that would stop it. Read this before committing. Filter by `action` and page with `after` and `limit`. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). An unknown import gives `import_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Apply a ready import in the background",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/imports/{importId}/commit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "imports",
                ":importId",
                "commit"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "importId",
                  "value": "<importId>"
                }
              ]
            },
            "description": "Applies a previewed import in the background and answers 202 with the import moved to `committing`; poll the import to see it finish. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_state` (409) when the import is not `ready`, or when every row carries an error so there is nothing to apply, and `import_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Drop an import that has not been committed",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/imports/{importId}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "imports",
                ":importId",
                "cancel"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "importId",
                  "value": "<importId>"
                }
              ]
            },
            "description": "Drops an import that has not been committed, leaving the catalog untouched. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_state` (409) when the import has already been committed, and `import_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "The merchant's API keys, without secrets",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/api_keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "api_keys"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The merchant's API keys with their environment, scopes, status and last use. Secrets are never included; a key's token exists only in the response that issued it. Only a person may manage keys, so a key — bound or not — is refused `consumer_token_required` (403); the caller must also have authority over the client (`not_client_member` or `insufficient_scope`, 403). A deployment without merchant keys configured answers `merchant_keys_disabled` (501)."
          },
          "response": []
        },
        {
          "name": "Issue an API key that acts as this merchant",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/api_keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "api_keys"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Only a signed-in member of the merchant's organization may issue a key; a key cannot mint more keys. The token is returned once."
          },
          "response": []
        },
        {
          "name": "Revoke one of the merchant's keys",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/api_keys/{keyId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "api_keys",
                ":keyId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "keyId",
                  "value": "<keyId>"
                }
              ]
            },
            "description": "Revokes one of the merchant's keys and answers 204; the key stops authenticating at once. Only a person may revoke a key, so a key is refused `consumer_token_required` (403), and the caller must have authority over the client. An unknown key gives `key_not_found` (404) and a deployment without merchant keys `merchant_keys_disabled` (501)."
          },
          "response": []
        },
        {
          "name": "The merchant's own webhook endpoints",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/webhook_endpoints",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "webhook_endpoints"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "A merchant endpoint receives the events that belong to this merchant: product.*, order.*, inventory.* and import.*. Tenant-wide events never reach it. A merchant-bound API key may manage these."
          },
          "response": []
        },
        {
          "name": "Register a webhook endpoint for this merchant; the signing secret is returned once",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/webhook_endpoints",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "webhook_endpoints"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Registers a webhook endpoint that receives only this merchant's events. The signing secret is returned here and nowhere else. `event_filters` narrows what is delivered; omit it for every event the merchant can see. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). A merchant-bound API key may register its own endpoints, which is the point; it still cannot touch the tenant-wide webhook surface. A missing body or a URL the service will not accept gives `invalid_request` (400)."
          },
          "response": []
        },
        {
          "name": "Stop deliveries to one of the merchant's endpoints",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/webhook_endpoints/{endpointId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "webhook_endpoints",
                ":endpointId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "endpointId",
                  "value": "<endpointId>"
                }
              ]
            },
            "description": "Stops deliveries to one of the merchant's endpoints and answers 204. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). An endpoint that is not this merchant's gives `endpoint_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Delivery attempts to the merchant's endpoints",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/webhook_deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "webhook_deliveries"
              ],
              "query": [
                {
                  "key": "endpoint_id",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Delivery attempts to this merchant's endpoints, newest first, with response code and error. Optional `endpoint_id` narrows it. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Settlement and marketing balances",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/marketing",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "marketing"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The client's settlement balance and its marketing balance. Settlement is sharded, so the figure is the sum across shards; a client that has neither account yet reads zero rather than an error. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Move the client's settlement money into its marketing account",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/marketing/fund",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "marketing",
                "fund"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Moves money from the client's settlement balance into its marketing account, where cashback rules draw from. Requires an `Idempotency-Key`; the funding id is derived from it, so a crash- retry replays the same posting instead of moving the money twice. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400), `insufficient_funds` (422) when the settlement balance does not cover it, and `idempotency_key_reuse` (409) when the key comes back with a different body."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_clients_clientId_marketing_fund\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_clients_clientId_marketing_fund to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Set a merchant's per-transaction commission rate (tenant admin)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/commission",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "commission"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The tenant's negotiated marketplace commission for this one merchant, applied to every catalog sale it makes. It overrides the tenant's default commission rate; a per-offering rate still beats it. Requires fees:manage, the same authority as the tenant-wide fee plan; a merchant cannot set its own commission."
          },
          "response": []
        },
        {
          "name": "List this merchant's own cashback and loyalty rules",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/reward_rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "reward_rules"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The merchant's own cashback and loyalty rules — the ones it owns, not the tenant-wide rules that may also apply to its sales. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Set a cashback or loyalty rule for this merchant, common or per-product",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/reward_rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "reward_rules"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Sets one of the merchant's own reward rules, either common to the whole shop or bound to one offering by `offering_id`; a per-product rule overrides the common one for that product. `kind: cashback` pays from the merchant's own marketing pot; `kind: accrual` issues tenant loyalty points, so it carries no funding merchant even though the merchant configured it. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400) for any other kind or parameters the rule engine rejects, and `rule_exists` (409) when the merchant already has a rule in that slot."
          },
          "response": []
        },
        {
          "name": "Turn off one of this merchant's own rules",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/reward_rules/{ruleId}/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "reward_rules",
                ":ruleId",
                "disable"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "ruleId",
                  "value": "<ruleId>"
                }
              ]
            },
            "description": "Retires a rule the merchant owns, freeing its slot so a new rate can be set. Grants already made stand. A rule the merchant does not own is not found."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Orders",
      "description": "Multi-line orders against the catalog, and their fulfilment state.",
      "item": [
        {
          "name": "The merchant's order book, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "orders"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The merchant's order book, newest first by `created_at` and optionally narrowed by `status`. Ties are broken by id so a page boundary never skips or repeats an order. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "One of the merchant's orders",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/orders/{orderId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "orders",
                ":orderId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "orderId",
                  "value": "<orderId>"
                }
              ]
            },
            "description": "One of the merchant's orders with its lines, commission and payment. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). An order that is not this merchant's gives `order_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Cancel an open order and release its stock",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/orders/{orderId}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "orders",
                ":orderId",
                "cancel"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "orderId",
                  "value": "<orderId>"
                }
              ]
            },
            "description": "Cancels an open order from the merchant's side and releases the stock its lines reserved. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `order_not_found` (404) and `invalid_state` (409) when the order has already been paid or cancelled."
          },
          "response": []
        },
        {
          "name": "Put a paid order's units back on the shelf (after a refund), once",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/orders/{orderId}/restock",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "orders",
                ":orderId",
                "restock"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                },
                {
                  "key": "orderId",
                  "value": "<orderId>"
                }
              ]
            },
            "description": "Puts a paid order's units back on the shelf after the money has been refunded — it moves stock, never money, and the refund is a separate call. It runs once: a second attempt is refused `invalid_state` (409), as is an order that is not `paid`. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). An unknown order gives `order_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "The calling shopper's orders, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "The calling shopper's own orders, newest first by `created_at`, ties broken by id so a page boundary never skips or repeats an order. Requires `payments:read` and a consumer token: an API key or staff session has no shopper self and is refused `consumer_token_required` (403). Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Reserve a cart from one merchant at catalog prices",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "Stock is reserved for each line (unless the variant does not track inventory or allows backorder) until the order is paid, canceled or expires. 422 insufficient_stock names the line that cannot be promised."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_orders\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_orders to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "One of the shopper's orders",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/{orderId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderId"
              ],
              "variable": [
                {
                  "key": "orderId",
                  "value": "<orderId>"
                }
              ]
            },
            "description": "One of the calling shopper's orders with its lines and payment. Requires `payments:read` and a consumer token (`consumer_token_required`, 403); an order that is not the caller's gives `order_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Pay an open order with one instant payment",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/{orderId}/pay",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderId",
                "pay"
              ],
              "variable": [
                {
                  "key": "orderId",
                  "value": "<orderId>"
                }
              ]
            },
            "description": "Idempotent by order: calling it again returns the same payment. A payment refusal (insufficient funds, limits) leaves the order open."
          },
          "response": []
        },
        {
          "name": "Cancel an open order and release its stock",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/{orderId}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderId",
                "cancel"
              ],
              "variable": [
                {
                  "key": "orderId",
                  "value": "<orderId>"
                }
              ]
            },
            "description": "Cancels the shopper's own open order and releases the stock it reserved. Requires `payments:write` and a consumer token (`consumer_token_required`, 403). Refuses `order_not_found` (404) and `invalid_state` (409) once the order has been paid or cancelled."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Discovery",
      "description": "The public read surface over published clients and offerings, plus purchase and subscribe.",
      "item": [
        {
          "name": "Browse and search the client directory",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/explore/clients",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "explore",
                "clients"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "category",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Browses the public client directory: only clients the platform has made active appear. Search with `q` and narrow with `category`. Requires `catalog:read`; `limit` is clamped to 1-100 and `cursor` pages it."
          },
          "response": []
        },
        {
          "name": "A client's public profile with its published offerings",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/explore/clients/{clientId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "explore",
                "clients",
                ":clientId"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "A client's public profile with its published offerings. Drafts and archived offerings are not included, and variants carry the shopper's view only: no cost, markup or stock counts. Requires `catalog:read`; an unknown or inactive client gives `client_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "A published offering with its client",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/explore/offerings/{offeringId}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "explore",
                "offerings",
                ":offeringId"
              ],
              "variable": [
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "One published offering with the client selling it. Requires `catalog:read`. An offering that is not published, or whose client is not active, gives `offering_not_found` (404) — the shopper's view never reveals a draft."
          },
          "response": []
        },
        {
          "name": "Buy a one-off item; the catalog price is the only price",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/explore/offerings/{offeringId}/purchase",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "explore",
                "offerings",
                ":offeringId",
                "purchase"
              ],
              "variable": [
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "Buys one unit of a published item for the calling consumer. The catalog is the only source of price: no amount is accepted from the client. Since ADR-0035 the purchase is an order of one unit of the item's single variant, so stock is reserved and sold exactly as a cart is, and the response is still the payment. Requires `payments:write`, an `Idempotency-Key` and a consumer token (`consumer_token_required`, 403). Refuses `offering_not_found` (404), `not_an_item` (422) for a plan — use subscribe — `insufficient_stock` (422), `insufficient_funds` (422), and `idempotency_key_reuse` (409)."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_explore_offerings_offeringId_purchase\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_explore_offerings_offeringId_purchase to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Subscribe to a plan; amount and interval come from the catalog",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/explore/offerings/{offeringId}/subscribe",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "explore",
                "offerings",
                ":offeringId",
                "subscribe"
              ],
              "variable": [
                {
                  "key": "offeringId",
                  "value": "<offeringId>"
                }
              ]
            },
            "description": "Subscribes the calling consumer to a published plan. Amount and interval come from the catalog, and the marketplace commission is fixed at signup so a later rate change cannot move a mandate somebody agreed to. Requires `subscriptions:write`, an `Idempotency-Key` and a consumer token (`consumer_token_required`, 403). Refuses `offering_not_found` (404), `not_a_plan` (422) for an item — use purchase — `invalid_request` (400), and `idempotency_key_reuse` (409)."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_explore_offerings_offeringId_subscribe\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_explore_offerings_offeringId_subscribe to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Payouts",
      "description": "Moving a client's settlement money out of the loop. Books first, the transfer follows.",
      "item": [
        {
          "name": "The client's payout statement, newest first",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/payouts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "payouts"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "The client's payout statement, newest first, each row with the amount that left settlement, the fee the wallet kept, the rail and the ledger transaction behind it. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). `limit` is clamped to 1-100."
          },
          "response": []
        },
        {
          "name": "Pay out settlement money (books first, transfer follows)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": ""
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/clients/{clientId}/payouts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "clients",
                ":clientId",
                "payouts"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Draws the merchant's settlement money to the tenant treasury and records the payout; the external transfer is executed off-platform, so this call moves the books, not the bank. Omit `amount` to pay out the whole settlement balance. Requires an `Idempotency-Key`, and the payout id is derived from it, so key on the payout *period* rather than the moment: a retry after a timeout pays once. Needs authority over the client — membership of its organization, an API key bound to this client, or tenant staff holding `clients:manage`; anything else is refused `not_client_member` or `insufficient_scope` (403), and an unknown client `client_not_found` (404). Refuses `invalid_request` (400) for a negative amount, `nothing_to_pay` (422) when there is no settlement balance, `insufficient_funds` (422) when the amount exceeds it, and `idempotency_key_reuse` (409) when the key comes back with a different body."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const key = pm.collectionVariables.get(\"idempotency_post__v1_clients_clientId_payouts\");",
                  "if (typeof key !== \"string\" || !/^[\\x21-\\x7e]{1,200}$/.test(key) || key.includes(\"{{\")) {",
                  "  console.error(\"Set idempotency_post__v1_clients_clientId_payouts to a saved logical-operation key. Keep it and the payload unchanged for retries; replace it for a new operation.\");",
                  "  pm.execution.skipRequest();",
                  "} else {",
                  "  pm.request.headers.upsert({ key: \"Idempotency-Key\", value: key });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Endpoint registration, the emitted event log, and redelivery.",
      "item": [
        {
          "name": "Delivery attempt log (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhook_deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhook_deliveries"
              ],
              "query": [
                {
                  "key": "endpoint_id",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Every delivery attempt with its attempt number, HTTP response code and error, newest first — the log to read when a receiver is not seeing events. Requires `webhooks:read` or `webhooks:manage` and a machine or staff principal; a user token is refused `forbidden` (403). Optional `endpoint_id` narrows to one endpoint. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Register a webhook endpoint; the signing secret is returned once",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/webhook_endpoints",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhook_endpoints"
              ]
            },
            "description": "Registers a tenant-wide webhook endpoint. The signing secret is returned in this response and nowhere else — store it now, because there is no call that reads it back. `event_filters` narrows what is delivered; omit it to receive every event type. Requires `webhooks:manage`; a missing body or a URL the service will not accept gives `invalid_request` (400)."
          },
          "response": []
        },
        {
          "name": "List webhook endpoints",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhook_endpoints",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhook_endpoints"
              ]
            },
            "description": "The tenant's webhook endpoints with their filters and status. Signing secrets are never included. Requires `webhooks:read` or `webhooks:manage`."
          },
          "response": []
        },
        {
          "name": "List emitted events",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhook_events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhook_events"
              ],
              "query": [
                {
                  "key": "type",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "since",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Events this tenant has emitted, with the payload each delivery carried. Requires `webhooks:read` or `webhooks:manage`. Optional `type` and `since` narrow the list; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Re-enqueue an event to all matching endpoints",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhook_events/{eventId}/redeliver",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhook_events",
                ":eventId",
                "redeliver"
              ],
              "variable": [
                {
                  "key": "eventId",
                  "value": "<eventId>"
                }
              ]
            },
            "description": "Re-enqueues one event to every endpoint whose filters match it, and answers with how many deliveries were enqueued. Requires `webhooks:manage`; an unknown event gives `event_not_found` (404). This is a duplicate delivery on purpose, so the receiver must be idempotent on the event id."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Ledger and reporting",
      "description": "Tenant-wide transaction explorer, balance totals, movement metrics and the fee plan.",
      "item": [
        {
          "name": "Tenant-wide transaction explorer with resolved legs (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "type",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "The tenant's transactions with every leg resolved to its owner, purpose, direction and commodity — the explorer behind the admin console. Requires `ledger:read` and a machine or staff principal; a user token is refused `forbidden` (403). Optional `type` narrows to one movement kind. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Balance totals per owner type and purpose — the float equation view (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/ledger/summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ledger",
                "summary"
              ]
            },
            "description": "Balances grouped by owner type, purpose and commodity, with the account count behind each row and the tenant's fee plan — the float view an operator reconciles against. Requires `ledger:read` and a machine or staff principal; a user token is refused `forbidden` (403)."
          },
          "response": []
        },
        {
          "name": "Money movement, revenue, exposure and reconciliation for a period (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/ledger/metrics",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ledger",
                "metrics"
              ],
              "query": [
                {
                  "key": "window",
                  "value": "",
                  "description": "Reporting period ending now. The previous period of the same length is returned alongside for comparison.",
                  "disabled": true
                }
              ]
            },
            "description": "The reporting view behind the console dashboard. Balances alone answer \"what is the loop holding right now\"; this answers \"how much money did the loop move, what did it earn on it, what is it exposed to, and do the books still net to zero\"."
          },
          "response": []
        },
        {
          "name": "Update the tenant fee plan (audited; admin)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/fee_plan",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "fee_plan"
              ]
            },
            "description": "Replaces the fee rule for each money path the body names and leaves every path it does not name untouched, so naming a path is what asks for it to change. Requires `fees:manage` and a machine or staff principal; a user token is refused `forbidden` (403). Out-of-range basis points or amounts give `invalid_request` (400) and an unknown tenant `tenant_not_found` (404). The change is audited with before and after."
          },
          "response": []
        },
        {
          "name": "Tenant audit trail (admin)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/audit_log",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "audit_log"
              ],
              "query": [
                {
                  "key": "action",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "The tenant's audit trail, newest first: who changed what, with the before and after of each mutation. Requires `audit:read` and a machine or staff principal; a user token is refused `forbidden` (403). Optional `action` narrows to one kind of change; the cursor is the last sequence number you saw and `limit` is clamped to 1-100."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Operations and audit",
      "description": "The audit trail and the vendor-support access grants that gate WalletD's own staff.",
      "item": [
        {
          "name": "List WalletD vendor-support approvals (partner admin only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/support_access_grants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "support_access_grants"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Expired and revoked grants remain in the response as operational and audit evidence. A partner's own support role does not use these grants.\n\n**Console-only.** This path is not routed at the public API gateway: it is reachable from the partner administration console and from inside the deployment, never with a tenant API key over the internet. It additionally requires a partner administrator — a staff principal that is neither a platform nor a WalletD vendor-support identity."
          },
          "response": []
        },
        {
          "name": "Approve short-lived WalletD vendor-support access (partner admin only)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/support_access_grants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "support_access_grants"
              ]
            },
            "description": "Approves one named WalletD support engineer to touch this tenant's production data for a bounded window, against a ticket reference and a stated reason. Requires a partner administrator: a staff session that is neither a platform nor a WalletD vendor-support principal and that holds `support_access:manage`; anyone else is refused `partner_admin_required` (403). A missing body or an expiry the service will not accept gives `invalid_request` (400), and a second grant for an engineer who already holds a live one `support_access_already_active` (409). While the grant stands, every request that engineer makes is recorded against it.\n\n**Console-only.** This path is not routed at the public API gateway: it is reachable from the partner administration console and from inside the deployment, never with a tenant API key over the internet. It additionally requires a partner administrator — a staff principal that is neither a platform nor a WalletD vendor-support identity."
          },
          "response": []
        },
        {
          "name": "Get the calling WalletD vendor-support identity's active grant",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/support_access_grants/current",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "support_access_grants",
                "current"
              ]
            },
            "description": "This is the only tenant endpoint a WalletD vendor-support identity may call without an active grant. It lets the console present a clear waiting-for-approval state instead of failing into an error page.\n\n**Console-only.** This path is not routed at the public API gateway. It is called by the WalletD support console to show a waiting-for-approval state, and by nothing a partner integrates against."
          },
          "response": []
        },
        {
          "name": "Revoke WalletD vendor-support access immediately (partner admin only)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/support_access_grants/{grantId}/revoke",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "support_access_grants",
                ":grantId",
                "revoke"
              ],
              "variable": [
                {
                  "key": "grantId",
                  "value": "<grantId>"
                }
              ]
            },
            "description": "Ends a support grant immediately; the engineer's next request is refused. Requires a partner administrator holding `support_access:manage`, so anyone else is refused `partner_admin_required` (403), and an unknown grant gives `support_access_grant_not_found` (404). The access already recorded under the grant stays readable after it is revoked.\n\n**Console-only.** This path is not routed at the public API gateway: it is reachable from the partner administration console and from inside the deployment, never with a tenant API key over the internet. It additionally requires a partner administrator — a staff principal that is neither a platform nor a WalletD vendor-support identity."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Platform administration",
      "description": "Tenant provisioning and per-tenant health. Platform staff only; not part of a partner integration.",
      "item": [
        {
          "name": "Provision a tenant with its system accounts (platform staff only)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/tenants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenants"
              ]
            },
            "description": "Provisions a partner loop: the tenant's ledger is created first, then the tenant row, so the tenant id is minted alongside its books. Requires a platform staff token holding `platform:manage`; every other principal is refused `platform_scope_only` (403). A missing body, a blank name or a currency the service rejects give `invalid_request` (400). The change is audited."
          },
          "response": []
        },
        {
          "name": "List tenants (platform staff only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tenants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenants"
              ]
            },
            "description": "Every tenant on this deployment, newest first, with its fee plan and commission mode. Requires a platform staff token holding `platform:manage`; anyone else is refused `platform_scope_only` (403)."
          },
          "response": []
        },
        {
          "name": "Per-tenant health snapshot — balances, fee plan, ledger invariants (platform staff only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/tenants/{tenantId}/summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "tenants",
                ":tenantId",
                "summary"
              ],
              "variable": [
                {
                  "key": "tenantId",
                  "value": "<tenantId>"
                }
              ]
            },
            "description": "The tenant's balance rollup by owner type, purpose and commodity, plus the one invariant a caller can check without holding the books: every commodity must net to zero across the whole ledger. `invariants_ok` is false and `violations` names the commodity when it does not. The deeper checks (entry sums, floors, checkpoints) belong to `ledgerd verify`, not here. Requires `platform:manage`; refuses `platform_scope_only` (403) and `tenant_not_found` (404)."
          },
          "response": []
        },
        {
          "name": "Whole-platform ledger view — every tenant's position and health (global admin only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/platform/overview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "platform",
                "overview"
              ]
            },
            "description": "One row per tenant with its user count, 30-day movement, treasury float, outstanding liabilities and whether its books net to zero, plus totals per currency. Amounts are minor units of each tenant's own currency and are never summed across currencies."
          },
          "response": []
        },
        {
          "name": "Ecosystem clients across all tenants, the review queue (global admin only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/platform/clients",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "platform",
                "clients"
              ],
              "query": [
                {
                  "key": "tenant_id",
                  "value": "",
                  "description": "Restrict a cross-tenant listing to one tenant",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Ecosystem clients across every tenant — the moderation queue. Filter by `status` and by tenant. Requires a platform staff token holding `platform:read`, which only the global admin role carries; everyone else, including platform provisioning staff, is refused `global_read_only` (403). The read is recorded in the cross-tenant access log. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Let a pending client publish to Explore (platform staff only)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/platform/clients/{clientId}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "platform",
                "clients",
                ":clientId",
                "approve"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Lets a pending client publish its offerings to Explore. This is a moderation write, not a report, so it needs `platform:manage` rather than the read scope; anything else is refused `platform_scope_only` (403). An unknown client gives `client_not_found` (404) and a client that is not in a state which can become active `invalid_transition` (409). Audited with the platform actor."
          },
          "response": []
        },
        {
          "name": "Hide a client's whole catalog from Explore (platform staff only)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/platform/clients/{clientId}/suspend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "platform",
                "clients",
                ":clientId",
                "suspend"
              ],
              "variable": [
                {
                  "key": "clientId",
                  "value": "<clientId>"
                }
              ]
            },
            "description": "Suspension is moderation, not confiscation: the catalog disappears from Explore while settled money stays settled and a payout still works."
          },
          "response": []
        },
        {
          "name": "Cross-tenant transaction explorer (global admin only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/platform/transactions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "platform",
                "transactions"
              ],
              "query": [
                {
                  "key": "tenant_id",
                  "value": "",
                  "description": "Restrict a cross-tenant listing to one tenant",
                  "disabled": true
                },
                {
                  "key": "type",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Every tenant's transactions with their resolved legs, optionally narrowed by tenant and by type. Requires a platform staff token holding `platform:read`; anyone else is refused `global_read_only` (403). The read is recorded in the cross-tenant access log. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Wallet users across all tenants (global admin only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/platform/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "platform",
                "users"
              ],
              "query": [
                {
                  "key": "tenant_id",
                  "value": "",
                  "description": "Restrict a cross-tenant listing to one tenant",
                  "disabled": true
                },
                {
                  "key": "query",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Wallet users across every tenant, optionally narrowed by tenant and by a free-text `query`. Requires a platform staff token holding `platform:read`; anyone else is refused `global_read_only` (403). The read is recorded in the cross-tenant access log. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        },
        {
          "name": "Subscriptions across all tenants (global admin only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/platform/subscriptions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "platform",
                "subscriptions"
              ],
              "query": [
                {
                  "key": "tenant_id",
                  "value": "",
                  "description": "Restrict a cross-tenant listing to one tenant",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Last seen id from the previous page. The listing resumes after that row, so an id that belongs to no row in the listing is refused `invalid_request` (400) rather than answered with an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Subscriptions across every tenant, optionally narrowed by tenant and by status. Requires a platform staff token holding `platform:read`; anyone else is refused `global_read_only` (403). The read is recorded in the cross-tenant access log. Cursor paged: pass the last id you saw as `cursor`; `limit` is clamped to 1-100 and defaults to 50."
          },
          "response": []
        }
      ]
    },
    {
      "name": "System",
      "description": "Liveness. Unauthenticated, and the only path outside the bearer scheme.",
      "item": [
        {
          "name": "Liveness probe",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/healthz",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "healthz"
              ]
            },
            "description": "Liveness probe. Unauthenticated, and it answers `{\"status\":\"ok\"}` as soon as the process is serving HTTP. It does not touch the database, authsvc or the ledger of record, so a 200 here means the container is up, not that money can move."
          },
          "response": []
        }
      ]
    }
  ]
}
