{
  "openapi": "3.1.0",
  "info": {
    "title": "MolnPay API",
    "version": "2026-08-01",
    "summary": "Keyless crypto custody and payments for businesses: a deposit address per customer on every chain, balances, sweeps, payouts, invoices and webhooks.",
    "description": "# MolnPay Wallet API — integration guide\n\nA multi-tenant **custodial wallet service**. You create customers, we derive a\ndeposit address per customer per chain, we watch the chains, credit an internal\ndouble-entry ledger when deposits confirm, notify you by signed webhook, sweep\nfunds into your master wallet, and send payouts on your instruction.\n\n**What it is not:** not an exchange, not a trading venue, not a fiat on-ramp. It\nmoves crypto and keeps an accurate ledger. Nothing else.\n\n- Base URL: `https://api.molnpay.com`\n- Machine-readable contract: [`openapi.yaml`](./openapi.yaml)\n- Runnable verifiers: [`examples/`](./examples/)\n\n---\n\n## 1. Authentication\n\n```\nAuthorization: Bearer mp6abeada4ef98a41267d194582a620b19a42f8e2c27a6aef892830a8778d99a02\n```\n\nA key is a prefix plus **64 lowercase hex characters** (32 random bytes, 256\nbits) and nothing else:\n\n| Part | Meaning |\n|---|---|\n| `mp` | a **live** key |\n| `mptest` | a **test** key — no chain behind it (the chain is simulated, §10) and every event carries `livemode:false` |\n| the 64 hex | the secret itself |\n\nThere are no separators and no embedded fields, so the whole token matches\n`[0-9a-z]+` — nothing in it needs quoting, escaping or encoding, wherever you\nput it. **Put it in the `Authorization` header and nowhere else.** In\nparticular never place it in a query string: URLs end up in access logs, proxy\nlogs, browser history and `Referer` headers, and a key that lands in any of\nthose is a leaked key regardless of how clean its characters are.\n\n**The key's kind is not in the string.** Every key is created as one of\n`checkout`, `payout` or `wallet`, and that is a property of the key, not of the\ntoken — authorization is never read off a string you supply. `GET /v1/ping`\nreports it.\n\n| Kind | Reaches |\n|---|---|\n| `checkout` | invoices and the hosted checkout |\n| `payout` | **money out** — payouts and their approvals, plus balances (it reads what it can pay from) and payout history |\n| `wallet` | customers, deposit addresses, deposits, balances, sweeps, and read-only payout history |\n\n`GET /v1/ping`, `GET /v1/chains` and `GET /v1/balances` (the project treasury)\naccept any kind; a customer's or a wallet's balances need a `wallet` or\n`payout` key.\n\nCalling a route with the wrong kind is `403 wrong_key_kind` — deliberately not\n`401`: you are authenticated, and \"use your other key\" is something you can act\non. The point of the split is that the key your storefront carries cannot send\nfunds anywhere, whatever scopes it was granted. **The kind is checked before\nscopes**, so a `checkout` key that was somehow granted `payouts:create` still\ngets `wrong_key_kind` on `POST /v1/payouts`, never `missing_scope`; the fix is\na different key, not a different grant.\n\n**The full key is shown exactly once, when it is created.** There is no endpoint\nthat can return it afterwards — not with elevated privileges, not for the account\nowner. If it is lost, roll it. What we keep is the peppered hash, plus a public\ndisplay handle (`mp6abeada4` — the prefix and the first 8 hex) so you can tell\nyour keys apart in a console or a log without ever writing the key down.\n\n**Rolling.** A roll issues a new key and sets the old one to expire (24h by\ndefault). Both work during the overlap, so you can deploy the new key before the\nold one dies. Revoking is immediate.\n\n**Scopes.** Each key carries a subset of:\n`customers:read` `customers:write` `wallets:read` `wallets:create` `balances:read`\n`deposits:read` `sweeps:read` `sweeps:create` `payouts:read` `payouts:create`\n`webhooks:read` `webhooks:write` `webhooks:replay`. Wildcards `*` and\n`wallets:*` are supported.\n\n**IP allowlist.** A key may be pinned to CIDRs. Empty means any source.\n\n**Signing.** Moving funds (`payouts:create`, `sweeps:create`) additionally\nrequires signing to be enabled at three levels: globally, for your project, and\nfor that specific key. This means you can issue a deliberately **read-only key**\nfor reporting systems. `403 signing_disabled` means one of the three is off —\nand `GET /v1/ping` tells you which, in `signing_blocked_by`\n(`service` / `project` / `key`), so you can ask the operator for the right thing\ninstead of guessing.\n\nVerify a key with `GET /v1/ping` — it echoes your kind, scopes, network, and\nwhether signing is on.\n\n---\n\n## 2. First call\n\n```bash\ncurl https://api.molnpay.com/v1/ping \\\n  -H \"Authorization: Bearer $WALLET_KEY\"\n```\n\n```json\n{\n  \"object\": \"ping\",\n  \"tenant_id\": \"8f1c...\",\n  \"tenant_slug\": \"my-project\",\n  \"key_id\": \"mptest6abeada4\",\n  \"env\": \"test\",\n  \"kind\": \"wallet\",\n  \"scopes\": [\"customers:write\", \"wallets:create\", \"balances:read\"],\n  \"signing_enabled\": false,\n  \"signing_blocked_by\": [\"service\", \"key\"],\n  \"network\": \"testnet\",\n  \"enabled_chains\": [\"tron\", \"bsc\", \"polygon\"]\n}\n```\n\n---\n\n## 3. Money representation — read this twice\n\n**Every amount is a decimal STRING. Never a JSON number.**\n\n1 ETH is `1000000000000000000` wei, which is larger than JavaScript's\n`Number.MAX_SAFE_INTEGER` (2^53 ≈ 9.007e15). Parsing it as a number silently\ncorrupts it. Use `BigInt`, `decimal.Decimal`, `BigDecimal` — whatever your\nlanguage offers — end to end.\n\nEvery balance is returned in both forms so you never have to convert yourself:\n\n```json\n{\n  \"asset\": { \"chain\": \"bsc\", \"symbol\": \"USDT\", \"decimals\": 18, \"contract\": \"0x55d3…\" },\n  \"available\": \"1500000000000000000\",\n  \"available_decimal\": \"1.5\",\n  \"locked\": \"0\",\n  \"total\": \"1500000000000000000\"\n}\n```\n\n### ⚠ USDT and USDC on BSC have 18 decimals. Everywhere else, 6.\n\nThis is a real property of those token contracts, not our choice.\n\n| Chain | USDT decimals | `\"1000000\"` means |\n|---|---|---|\n| Ethereum, Tron, Solana, Polygon, Arbitrum, Base, Avalanche, TON | 6 | 1 USDT |\n| **BSC** | **18** | **0.000000000001 USDT** |\n\nBecause getting this wrong is a 12-orders-of-magnitude error, `POST /v1/payouts`\n**requires you to echo the asset's `decimals`** and returns\n`400 decimals_mismatch` (with both values) if it disagrees. Read `decimals` from\n`GET /v1/chains`. Never hardcode 6.\n\n---\n\n## 4. The happy path\n\n### 4.1 Create a customer\n\n```bash\ncurl -X POST https://api.molnpay.com/v1/customers \\\n  -H \"Authorization: Bearer $WALLET_KEY\" -H \"Content-Type: application/json\" \\\n  -d '{\"reference_id\":\"user_4471\",\"email\":\"u@example.com\"}'\n```\n\n```json\n{ \"object\":\"customer\", \"id\":\"9ab3…\", \"reference_id\":\"user_4471\",\n  \"email\":\"u@example.com\", \"status\":\"active\", \"metadata\":{},\n  \"created_at\":\"2026-08-01T10:00:00.000Z\" }\n```\n\n`reference_id` is your own user id. Re-posting the same one returns the **existing**\ncustomer with `200` instead of creating a duplicate.\n\n### 4.2 Get a deposit address\n\n```bash\ncurl -X POST https://api.molnpay.com/v1/wallets \\\n  -H \"Authorization: Bearer $WALLET_KEY\" -H \"Content-Type: application/json\" \\\n  -d '{\"customer_id\":\"9ab3…\",\"chain\":\"tron\"}'\n```\n\n```json\n{ \"object\":\"wallet\", \"id\":\"7c1e…\", \"customer_id\":\"9ab3…\", \"chain\":\"tron\",\n  \"role\":\"customer\", \"status\":\"active\", \"address\":\"TR7NHq…\", \"memo\":null,\n  \"created_at\":\"2026-08-01T10:00:05.000Z\" }\n```\n\nThere is **one wallet per (customer, chain)**, enforced by the database. Calling\nthis again returns the same address with `200`. That is deliberate: deriving a\nsecond address would split the customer's deposits across addresses your UI does\nnot show.\n\n**TON requires the `memo`.** If `memo` is non-null, the sender must include it or\nthe funds are unattributable.\n\n**Which chains, and what kind of address.** Read `GET /v1/chains` before you\nrender a picker — it is already filtered to what the platform lists for you.\nEach chain carries:\n\n- `account_kind` — `contract` is a keyless smart account: **the same address on\n  every EVM chain marked `contract`**, no private key anywhere. `eoa` is a\n  keypair the platform's signer holds. Keypair chains (Bitcoin, Dogecoin,\n  Solana, TON, …) are listed only when the operator has switched them on;\n  today the platform issues smart accounts only.\n- `status` (`live` | `paused` | `coming_soon`) and `status_note`, the\n  operator's reason in their words. Show the note verbatim.\n- `deposits_enabled` at the chain level, and per asset under `assets[]`. A\n  new address is issued only when both are true.\n\nA request for a **new** address on a chain that is not issuing is refused with\n`422` and a code that names the level: `account_kind_disabled` (the kind of\naddress that chain hands out is switched off), `chain_not_live_yet` or\n`chain_deposits_disabled`. A quote for a coin whose own deposits are off\n(invoices) fails with `asset_deposits_disabled`. **An address the customer\nalready holds is always returned**, whatever the switches say, and funds sent\nto it keep being credited — the one exception is a coin the operator has\n*archived* (a compromised contract), whose transfers are not credited.\n\n### 4.3 The customer sends funds → you get webhooks\n\nRegister where they go first (§9.6 has the whole lifecycle):\n\n```bash\ncurl -X POST https://api.molnpay.com/v1/webhooks \\\n  -H \"Authorization: Bearer $WALLET_KEY\" -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"https://example.com/molnpay/webhook\",\"event_types\":[\"deposit.confirmed\",\"deposit.orphaned\"]}'\n```\n\nThe `201` carries the endpoint's `secret` (`whsec_…`) **once**. Store it; §9.3\nis how you use it. Then:\n\n```\ndeposit.detected   seen on chain. NOT credited. Show as \"pending\".\ndeposit.confirmed  passed the chain's confirmation threshold. Credited. This is money.\ndeposit.orphaned   a confirmed deposit was reorged out. Reverse what you credited.\n```\n\n### 4.4 Read the balance\n\n```bash\ncurl \"https://api.molnpay.com/v1/customers/9ab3…/balances\" \\\n  -H \"Authorization: Bearer $WALLET_KEY\"\n```\n\n`available` is spendable. `locked` is reserved against an in-flight payout.\n\n### 4.5 Send a payout\n\n```bash\ncurl -X POST https://api.molnpay.com/v1/payouts \\\n  -H \"Authorization: Bearer $WALLET_KEY\" -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: 3f7a1c92-payout-8812\" \\\n  -d '{\"customer_id\":\"9ab3…\",\"chain\":\"tron\",\"asset\":\"USDT\",\"decimals\":6,\n       \"amount\":\"12500000\",\"to_address\":\"TQn9Y…\",\"reference\":\"wd_8812\"}'\n```\n\nReturns **202** — accepted, not yet sent. The transaction hash arrives later via\n`payout.submitted` / `payout.confirmed`.\n\nAbove your configured threshold a payout enters `pending_approval` and waits for\na human. That is a safety feature, not an error.\n\n### 4.6 Take a payment: invoice and hosted checkout\n\n```bash\ncurl -X POST https://api.molnpay.com/v1/invoices \\\n  -H \"Authorization: Bearer $CHECKOUT_KEY\" -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: order_8812\" \\\n  -d '{\"price_amount\":\"4990\",\"price_currency\":\"USD\",\"reference_id\":\"order_8812\",\n       \"title\":\"Order #8812\",\"return_url\":\"https://shop.example/thanks\"}'\n```\n\n```json\n{ \"object\":\"invoice\", \"id\":\"9f3c1b2e-…\", \"status\":\"open\",\n  \"price_amount\":\"4990\", \"price_currency\":\"USD\",\n  \"received_amount\":\"0\", \"excess_amount\":\"0\",\n  \"checkout_url\":\"https://pay.molnpay.com/i/ck_9f3c…\",\n  \"expires_at\":\"2026-09-16T12:30:00.000Z\",\n  \"addresses\":[{\"chain\":\"bsc\",\"address\":\"0x71C7…\",\"memo\":null}],\n  \"quotes\":[{\"chain\":\"bsc\",\"asset\":\"USDT\",\"contract\":\"0x55d3…\",\n             \"amount_atomic\":\"49900000000000000000\",\"amount\":\"49.90\",\n             \"decimals\":18,\"rate\":\"1.000000000000000000\",\"rate_source\":\"bitget_ws\"}] }\n```\n\nSend the payer to `checkout_url`. That is all the integration there is: the page\nshows the address, the QR and the amount, watches the chain, and returns them to\nyour `return_url`.\n\n**`price_amount` is minor units.** `4990`, not `49.90`. `amount_atomic` on each\nquote is what the payer actually sends; `amount` beside it is derived for\ndisplay and is never what a payment is checked against.\n\n**`decimals` is per (chain, asset), and it is not decorative.** USDT is an\n18-decimal token on BSC and a 6-decimal token everywhere else. The quoted amount\nis computed with the number in this field, so use that field rather than a\nconstant of your own.\n\n**Treat `checkout_url` like a bearer credential.** The token in it is the entire\nauthorisation for the checkout page — it is deliberately not the same value as\n`id`, so that pasting an invoice id into a support ticket hands over nothing.\n\n#### What the payer sees, and what you get\n\n| | |\n|---|---|\n| Paid within ±0.5% | `invoice.paid`. Fulfil here, and only here. |\n| Short by more than that | `invoice.underpaid`. The address stays live, the page shows the remainder with its own QR, and the window is extended (up to three times) so nobody loses it mid-transfer. **Do not fulfil.** A second `invoice.underpaid` is a new fact, not a duplicate. |\n| Over by more than that | `invoice.overpaid`, with `excess_amount` saying by how much. Your overpayment policy decides what happens to it: `keep` credits it to you, `refund` offers it back on the page. |\n| Nothing, in time | `invoice.expired`. Money arriving afterwards is still credited and marked `late`. |\n\n`invoice.pending` fires when a transfer is seen on chain but is below finality.\nIt is **not money** — a reorg can take it away — so use it to update a screen,\nnever to release goods.\n\n#### Cancelling\n\n```bash\ncurl -X POST https://api.molnpay.com/v1/invoices/9f3c1b2e-…/cancel \\\n  -H \"Authorization: Bearer $CHECKOUT_KEY\"\n```\n\nOnly while nothing has been received. Cancelling does **not** close the payment\naddress: it stays watched forever, so a late transfer is still detected and\ncredited rather than lost.\n\n---\n\n## 5. Idempotency\n\nSend `Idempotency-Key: <unique string>` on any POST. **It is required on\n`POST /v1/payouts`** — a retry without one can send funds twice, so the API\nrejects the request with `400 idempotency_key_required` rather than let you find\nout expensively.\n\n| Situation | Result |\n|---|---|\n| First use | request executes, response stored |\n| Same key, same body | the stored response replays, with `Idempotent-Replay: true` |\n| Same key, **different** body | `409 idempotency_key_reuse` |\n| Same key while the first is still running | `409 idempotency_in_progress` + `Retry-After` |\n\nKeys expire after 24h. Use a UUID, or something derived from your own record id.\n\n> A 5xx or a network timeout does **not** mean the request failed — only that you\n> did not see the answer. Retry with the same key.\n\n---\n\n## 6. Pagination\n\nCursor-based, not offset:\n\n```\nGET /v1/deposits?limit=25&cursor=MTI3NA\n```\n```json\n{ \"object\":\"list\", \"data\":[…], \"has_more\":true, \"next_cursor\":\"MTI3NA\" }\n```\n\nLoop until `has_more` is false, passing `next_cursor` each time. Cursors are\nopaque — do not construct or parse them. `limit` is 1–100, default 25.\n\nOffset paging is not offered on purpose: over an append-only log it both slows\ndown and silently skips rows when new ones arrive mid-scan.\n\n---\n\n## 7. Errors\n\n```json\n{ \"error\": \"insufficient_funds\",\n  \"code\": \"insufficient_funds\",\n  \"type\": \"invalid_request_error\",\n  \"message\": \"available 1200000, requested 5000000\",\n  \"param\": \"amount\",\n  \"request_id\": \"req_01J8…\" }\n```\n\nBranch on `code`. Quote `request_id` when reporting a problem.\n\n| HTTP | `type` | Meaning | Retryable? |\n|---|---|---|---|\n| 400 | `invalid_request_error` | malformed / failed validation | no — fix the request |\n| 401 | `authentication_error` | missing, malformed, expired or revoked key | no |\n| 402 | `invalid_request_error` | `insufficient_funds` | yes, after a deposit |\n| 403 | `authentication_error` | `wrong_key_kind`, `missing_scope`, IP allowlist, or `signing_disabled` — in that order | no |\n| 404 | `invalid_request_error` | not found — **also returned for another tenant's resource**, so a 404 never confirms existence | no |\n| 409 | `idempotency_error` | idempotency conflict or illegal state transition | see §5 |\n| 422 | `invalid_request_error` | valid JSON, rejected by the chain (bad address, `chain_not_enabled`) | no |\n| 429 | `rate_limit_error` | rate limit or quota | yes, after `Retry-After` |\n| 503 | `api_error` | a dependency is down | yes, with backoff |\n\nNotable codes: `decimals_mismatch`, `chain_not_enabled`, `unknown_asset`,\n`asset_disabled`, `invalid_address`, `self_payout_forbidden`,\n`destination_not_allowlisted`, `quota_exceeded`, `too_many_pending_payouts`,\n`payout_not_cancelable`, `signing_disabled`.\n\nAvailability codes, all `422`, all about a **new** address or payout (§4.2):\n`account_kind_disabled`, `chain_not_live_yet`, `chain_deposits_disabled`,\n`asset_deposits_disabled`, `asset_payouts_disabled`, `chain_unavailable`.\n\n---\n\n## 8. Rate limits\n\nPer-key, per minute; your limit is configurable. Unauthenticated requests get a\ndeliberately tiny per-IP budget. On 429, honour `Retry-After` and back off\nexponentially with jitter.\n\nSeparately, there is a **daily cap on address creation**. Every address we mint\nmust be watched forever, so this is metered independently of request rate. Cache\naddresses on your side; do not call `POST /v1/wallets` on every page load — it is\nidempotent, but it still counts.\n\n---\n\n## 9. Webhooks\n\n### 9.1 Envelope\n\n```jsonc\n{\n  \"id\": \"evt_01K3QW9Z8Y4M7F2N6X0P\",   // ULID — STABLE across retries AND replays\n  \"object\": \"event\",\n  \"type\": \"deposit.confirmed\",\n  \"api_version\": \"2026-08-01\",\n  \"created\": \"2026-08-01T12:34:56.789Z\",\n  \"livemode\": true,\n  \"tenant\": \"8f1c…\",\n  \"sequence\": 918273,                  // monotonic per tenant (gaps are normal)\n  \"data\": { /* the resource, identical to the REST GET response */ },\n  \"attempt\": 3,\n  \"delivery_id\": \"whd_01K3QWA2…\"\n}\n```\n\n`data` is the same shape the REST API returns, so you write one deserializer.\n\n### 9.2 Event catalog\n\n| Event | Meaning |\n|---|---|\n| `customer.created` | |\n| `wallet.created` | an address was derived |\n| `deposit.detected` | seen on chain — **not yet money** |\n| `deposit.confirmed` | credited to the customer's ledger balance |\n| `deposit.failed` | a `detected` transfer turned out not to be money: reverted, or the chain disagrees with the claim. `data.failure_reason` says why. Clear your \"pending\" |\n| **`deposit.orphaned`** | **a confirmed deposit was reorged out and reversed — you must reverse your own credit** |\n| `sweep.started` / `.confirmed` / `.failed` | consolidation into the master wallet. Does **not** change customer balances |\n| `payout.created` / `.approved` / `.rejected` / `.submitted` / `.confirmed` / `.failed` / `.canceled` | `data` is the payout object; `tx_hash` appears from `.submitted` on |\n| `conversion.completed` / `.failed` | auto conversion of one credited payment (Settings → Auto conversion). `failed` means kept as received, never money lost |\n| `balance.transferred` / `.transfer_reversed` | staff moved a settled balance between merchants with no chain transaction; `data.direction` says which side you are on |\n| `invoice.created` | priced, addressed and quoted |\n| `invoice.pending` | a transfer was seen on chain, below finality — **not yet money** |\n| **`invoice.paid`** | **final. Fulfil here, and only here** |\n| `invoice.underpaid` | short by more than ±0.5%. The address stays live for a top-up; **do not fulfil** |\n| `invoice.overpaid` | over by more than ±0.5%; `excess_amount` says by how much |\n| `invoice.expired` | the window closed without enough arriving |\n| `invoice.canceled` | withdrawn by you while unpaid |\n| `invoice.refunded` | the excess went back, to an address the payer confirmed |\n| `endpoint.test` | fired by the \"Send test event\" button |\n\n`invoice.underpaid` can arrive more than once on one invoice: each partial\npayment is a new fact about how much is still owed, not a repeat of the last.\nEvery other `invoice.*` event is emitted at most once per state change.\n\n### 9.3 Verifying the signature\n\n```\nMolnPay-Signature: t=1785312000,v1=5257a869e7ec…,v1=9f1c…\n```\n\nBuild the signed payload by concatenating:\n\n1. the value of `t` (Unix timestamp, **seconds**, ASCII decimal),\n2. the single character `.` (U+002E),\n3. the **raw HTTP request body bytes, exactly as received**.\n\nCompute `HMAC-SHA256(signed_payload, your_endpoint_secret)`, hex-encode\nlowercase, and compare — in **constant time** — against **every** `v1` value.\nOne match passes. Reject if `|now − t| > 300` seconds.\n\n> **Do not re-serialize the body before hashing.** `JSON.parse` → `JSON.stringify`\n> reorders keys and changes whitespace, and the HMAC will not match. This is the\n> number-one cause of signature failures.\n\nTwo `v1` values appear during a **secret rotation**: we sign with the old and the\nnew secret simultaneously (24h by default) so you can switch without a\ncoordinated cutover.\n\nCopy a working implementation from\n[`examples/verify-webhook.ts`](./examples/verify-webhook.ts) or\n[`examples/verify-webhook.py`](./examples/verify-webhook.py). Both are the same\nlogic the service uses and both include recipes for getting raw bytes out of\nExpress, Fastify, Next.js, FastAPI, Flask and Django.\n\nEvery delivery also carries: `MolnPay-Event-Id`, `MolnPay-Event-Type`,\n`MolnPay-Delivery-Id`, `MolnPay-Attempt`, `MolnPay-Api-Version`, and\n`Idempotency-Key` (equal to the event id).\n\n### 9.4 Delivery contract\n\n- **At least once.** Dedupe on `id`.\n- **Unordered.** Order on `sequence`; do not assume arrival order. Sequence is\n  monotonic per tenant but has gaps — compare, never count.\n- **Ack fast.** Return 2xx within 10 seconds, then process asynchronously. **A\n  slow 200 is recorded as a failed delivery** and will be retried.\n- **The REST API is the source of truth.** If an event and a `GET` disagree,\n  trust the `GET`.\n\n**Retries:** 10 attempts over roughly 46 hours, front-loaded (30s, 2m, 5m, 15m,\n1h, 3h, 6h, 12h, 24h) with jitter.\n\n**Auto-disable:** 5 consecutive exhausted deliveries disables your endpoint. An\nHTTP **410 Gone** disables it immediately — that is the documented way to say\n\"stop permanently\".\n\n**Nothing is lost while disabled.** Events still get a `skipped` delivery record,\nso after fixing your endpoint you re-enable it and bulk-replay the gap.\n\n### 9.5 Your endpoint URL must satisfy\n\n- `https` only, port 443 or 8443\n- resolves to a **public** IP — private, loopback, link-local, CGNAT and\n  cloud-metadata addresses are rejected\n- no credentials in the URL\n- **no redirects** — a 3xx is treated as a failure, not followed\n\nThese are re-checked on **every** send, not just at registration, because DNS is\nmutable.\n\n### 9.6 Managing endpoints from the API\n\nEverything below needs the `webhooks:write` scope (replay needs\n`webhooks:replay`; reads need `webhooks:read`). Any key kind may hold them.\n\n**Register.** `POST /v1/webhooks` with `url`, an optional `description`, and\n`event_types` (an empty list, the default, means *every* event — including\ntypes added later, so always keep a default branch). The `201` is the endpoint\nplus `secret`, shown **only here**. Up to 16 endpoints per project.\n\n**Prove it.** `POST /v1/webhooks/{id}/test` queues one `endpoint.test` through\nthe real pipeline — signed, SSRF-checked, retried — and answers `202` with a\n`delivery_id`. Read `GET /v1/webhook_deliveries/{delivery_id}` until `status`\nis `succeeded`; if it is not, `response_status`, `response_body` and\n`error_detail` say why in that order of usefulness.\n\n**Rotate.** `POST /v1/webhooks/{id}/rotate_secret` (`overlap_hours`, default\n24, max 168). Until `overlap_until`, every delivery carries two `v1` values and\neither secret verifies, so switch your receiver at your own pace. The new secret\nis shown only in that response.\n\n**Pause and resume.** `PATCH /v1/webhooks/{id}` with `status: \"paused\"` stops\nsends while still recording every event as a `skipped` delivery. `status:\n\"active\"` resumes. The same PATCH re-enables an endpoint we `disabled` (five\nconsecutive exhausted deliveries, or a 410 from you) and resets its strike\ncount.\n\n**Recover a gap.** After an outage: fix the receiver, re-enable, then\n`POST /v1/webhook_deliveries/replay` with `endpoint_id` (and optionally\n`status` / `since`). It re-sends up to 100 `failed`, `exhausted` or `skipped`\ndeliveries, oldest first, each as a **new** delivery (`replay_of`) of the\n**same** event — so a handler that dedupes on `event.id` is unaffected. Call it\nagain for a larger gap. One delivery at a time:\n`POST /v1/webhook_deliveries/{id}/replay`.\n\n**Audit.** `GET /v1/events` is every event your project emitted, ordered by\n`sequence` — the number the envelope carries. Reconciling \"did we miss one?\" is\na comparison against this list, never a guess. `GET /v1/events/{id}/deliveries`\nshows where each send of one event stood.\n\n**Delete.** `DELETE /v1/webhooks/{id}` removes the endpoint and its delivery\nhistory. The events themselves stay.\n\n---\n\n## 10. Test mode\n\nA new project starts in test mode: its keys are `mptest…`, every event carries\n`livemode: false`, and **no chain is behind it**. Nothing you send to a test\naddress on a real network or a testnet is ever seen, and nothing a test payout\n\"sends\" ever leaves. Instead you drive the chain yourself, and everything after\nit — the movement, the confirmation threshold, the ledger, the webhooks, the\nbalances, the payout state machine — is the production code path.\n\n### 10.1 Simulate a deposit\n\n```bash\ncurl -X POST https://api.molnpay.com/v1/test/deposits \\\n  -H \"Authorization: Bearer $WALLET_KEY\" -H \"Content-Type: application/json\" \\\n  -d '{\"customer_id\":\"9ab3…\",\"chain\":\"bsc\",\"asset\":\"BNB\",\"decimals\":18,\n       \"amount\":\"20000000000000000\"}'\n```\n\nThe customer must already hold an address on that chain (§4.2). You get the\ndeposit back as `GET /v1/deposits/{id}` would return it, and your endpoint\nreceives `deposit.detected` then `deposit.confirmed` — two events, in that\norder, exactly as a real transfer produces them. The balance is credited and a\npayout can be requested against it. `decimals` must be echoed, as on\n`POST /v1/payouts`, so the §3 mismatch is rehearsed here rather than in\nproduction.\n\nPass `\"confirm\": false` to stop at `detected`, then drive the pending deposit\nwith `/confirm` or `/fail`. `/orphan` acts on an already **confirmed** deposit:\n\n| Call | Result |\n|---|---|\n| `POST /v1/test/deposits/{id}/confirm` | credited; `deposit.confirmed` |\n| `POST /v1/test/deposits/{id}/fail` | never money; `deposit.failed` |\n| `POST /v1/test/deposits/{id}/orphan` | a **confirmed** deposit is reorged out and its credit reversed; `deposit.orphaned` |\n\nRehearse `orphan`. It is the one event integrators skip, and the one that\nleaves a ledger permanently wrong when it arrives unhandled in production.\n\n### 10.2 Payouts complete on their own\n\n`POST /v1/payouts` on a test key goes through every gate a live payout does —\nkind, scope, decimals, address validity, allowlist, the reserve — and then,\nwithin a few seconds, is settled as if broadcast and confirmed: you receive\n`payout.submitted` and `payout.confirmed` with a synthetic `tx_hash`, and the\ncustomer's `locked` balance clears. Above your auto-approve threshold it still\nwaits in `pending_approval` for a human, so rehearse your approval handling too.\n\nOf the three signing levels (§1), a test project is gated by the **key's** flag\nonly: the service and project switches exist to stop real money, and a test\npayout never reaches the signer. Mint the payout key with `signing_enabled:\ntrue` and `GET /v1/ping` answers `signing_enabled: true` on it.\n\n### 10.3 What differs from live\n\n- **Native coins only** — `GET /v1/chains` on a test key lists each chain's\n  native coin (BNB, ETH, AVAX…) and **no stablecoins**, because a token's\n  testnet deployment lives at a different contract address from mainnet and we\n  will not publish an address that points at nothing. The §4.5 example\n  (`\"asset\":\"USDT\"`) therefore cannot succeed on a test key, and\n  `decimals_differ_from_default` can never be `true` — the BSC trap is\n  mainnet-only. Rehearse it once against live with signing disabled.\n- **Chain metadata is mainnet's** — `chain_id`, `explorer_url` and the\n  `explorer_url` on every deposit and payout point at mainnet explorers, where\n  a synthetic hash does not exist. Do not build links from them while testing.\n- **The `/v1/test` routes refuse a live key** with `403 test_mode_only`. There\n  is no override: a route that can mint a credited deposit must not exist for a\n  project whose balances are real money.\n\nEverything else — customers, address derivation, balances, deposits, webhooks,\nidempotency, pagination, the payout gates — behaves identically in both modes.\nBuild and verify the whole flow in test mode, then re-verify the token-specific\npaths against live before going live.\n\n---\n\n## 11. Glossary\n\n| Term | Meaning |\n|---|---|\n| **tenant / project** | you, the integrating project |\n| **customer** | one of *your* end users. We never learn their identity |\n| **wallet** | a customer's holding on one chain |\n| **address** | the on-chain destination. A wallet can rotate addresses; retired ones stay monitored forever |\n| **master wallet** | your omnibus wallet per chain, where swept funds land |\n| **sweep** | moving funds from a customer's deposit address into your master wallet. **Does not change who owns them** |\n| **payout** | sending funds out to an external address |\n| **ledger** | the internal double-entry record. `available` + `locked` is what you owe a customer |\n\n---\n\n## 12. Common mistakes\n\n1. **Parsing amounts as floats or JSON numbers.** They are strings. Always.\n2. **Hardcoding 6 decimals.** USDT/USDC on BSC are 18.\n3. **Re-serializing the body before HMAC.** Use the raw bytes.\n4. **Trusting the payload before verifying the signature.** Verify first.\n5. **Not deduping on `event.id`.** Delivery is at-least-once.\n6. **Assuming event order.** Order on `sequence`.\n7. **Treating `deposit.detected` as money.** Only `confirmed` is.\n8. **Ignoring `deposit.orphaned`.** Reorgs are rare, not impossible, and unhandled they leave your ledger permanently wrong.\n9. **Omitting the TON memo.** Funds sent without it cannot be attributed.\n10. **Retrying a payout without an `Idempotency-Key`.** That is how you pay twice.\n11. **Polling instead of subscribing.** Webhooks are immediate; polling burns your rate limit.\n12. **Doing work before returning 2xx.** Ack, then process.\n",
    "contact": {
      "name": "MolnPay",
      "url": "https://molnpay.com"
    }
  },
  "servers": [
    {
      "url": "https://api.molnpay.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Meta",
      "description": "Key verification and chain metadata."
    },
    {
      "name": "Customers",
      "description": "Your end users."
    },
    {
      "name": "Wallets",
      "description": "Deposit addresses and master wallets."
    },
    {
      "name": "Balances",
      "description": "Ledger and on-chain balances."
    },
    {
      "name": "Deposits",
      "description": "Inbound transfers."
    },
    {
      "name": "Payouts",
      "description": "Outbound transfers to external addresses."
    },
    {
      "name": "Sweeps",
      "description": "Consolidation into your master wallet."
    },
    {
      "name": "Invoices",
      "description": "Hosted-checkout invoices: a price, a deadline, and an address per chain (merchant accounts)."
    },
    {
      "name": "Checkout",
      "description": "The PAYER's side of an invoice, and the only unauthenticated tenant-facing surface in this API — the invoice's `checkout_token` is the entire authorisation, so treat a checkout link like a bearer credential. The document is display-safe by construction: no metadata, no reference id, no internal ids, and no fee in any form."
    },
    {
      "name": "Billing",
      "description": "Your plan, your operational balance, and where to send money to top it up (merchant accounts)."
    },
    {
      "name": "Conversions",
      "description": "Auto conversion: per-network rules and what happened to every credited payment (merchant accounts)."
    },
    {
      "name": "Webhooks",
      "description": "The endpoints we deliver events to, the log of every delivery, and replay."
    },
    {
      "name": "Events",
      "description": "Every event your project emitted — the log the webhooks were fanned out from."
    },
    {
      "name": "API keys",
      "description": "Your project's keys. Reachable only with the console session (the signed-in owner), never with a key — a key cannot mint keys."
    },
    {
      "name": "Test mode",
      "description": "A test project's stand-in for the chain. A test project has no chain behind it: these routes write a deposit to one of your own addresses through the same code path the watcher uses, so every webhook, ledger entry and balance you see is the production behaviour. A test payout is settled automatically with a synthetic hash. Live keys are refused with `403 test_mode_only`."
    }
  ],
  "paths": {
    "/v1/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "Meta"
        ],
        "summary": "Verify a key and see what it can do",
        "responses": {
          "200": {
            "description": "Key context.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ping"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/chains": {
      "get": {
        "operationId": "listChains",
        "tags": [
          "Meta"
        ],
        "summary": "Supported chains, assets and their decimals",
        "description": "Read `decimals` from here rather than hardcoding. `decimals_differ_from_default` flags the assets whose scale deviates (USDT/USDC on BSC are 18, not 6).",
        "responses": {
          "200": {
            "description": "Chains.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ChainInfo"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/coins": {
      "get": {
        "operationId": "listCoins",
        "tags": [
          "Meta"
        ],
        "summary": "Every coin in the catalogue, including the ones not live yet",
        "description": "The platform's BREADTH, for a page that wants to show it. Unlike `GET /v1/chains`, which lists only what you may act on now, this includes coins that are switched off — each one carries `live: false`. Do not build a payment picker on this endpoint; use `GET /v1/chains` for that, or you will offer a coin the next call refuses. The catalogue is read at MAINNET whichever mode your key is in, because \"what do you carry\" has one answer — a test key sees the whole list here and `GET /v1/chains` tells it what it can actually transact with. Ordered `live` first, then by market rank with natives ahead of tokens — so the first rows are coins you can use today AND the ones people recognise. `totals` is computed over the WHOLE catalogue, before `chain` and before `limit`.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 120
            },
            "description": "How many coins to return. The totals are unaffected."
          },
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "live"
              ],
              "default": "all"
            },
            "description": "`live` returns only the coins the platform services today."
          },
          {
            "name": "chain",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            },
            "description": "One network's coins. `totals` still covers every network."
          },
          {
            "name": "group",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "symbol",
                "contract"
              ],
              "default": "symbol"
            },
            "description": "`symbol` collapses a coin carried on several networks into one row with a `chains` count — right for a wall of logos. `contract` returns every deployment, which is what a per-network table wants."
          }
        ],
        "responses": {
          "200": {
            "description": "The catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "coin_list"
                      ]
                    },
                    "totals": {
                      "$ref": "#/components/schemas/CoinTotals"
                    },
                    "returned": {
                      "type": "integer"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CoinSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/market/prices": {
      "get": {
        "operationId": "listPrices",
        "tags": [
          "Meta"
        ],
        "security": [],
        "summary": "Live USD prices for the assets we settle in",
        "description": "Public and unauthenticated — exchange rates are not tenant data, and the hosted checkout needs them before any session exists. Prices are cached for `ttl_seconds`; respect it rather than polling. `usd` is a STRING, like every other amount in this API. `age_seconds` says how old the quote is: past `ttl_seconds` we serve the cached price immediately and refresh behind the response, so a value older than the TTL is expected and is not an error. When the upstream market source is not configured the response is a 503 with `configured: false` and an empty list — callers should render whatever they last had rather than treat it as an error.",
        "parameters": [
          {
            "name": "symbols",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated symbols, e.g. `USDT,ETH`. Omit for all priced assets."
          }
        ],
        "responses": {
          "200": {
            "description": "Prices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "price_list"
                      ]
                    },
                    "configured": {
                      "type": "boolean"
                    },
                    "ttl_seconds": {
                      "type": "integer"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "symbol": {
                            "type": "string"
                          },
                          "usd": {
                            "type": "string",
                            "description": "Decimal string, never a number."
                          },
                          "age_seconds": {
                            "type": "integer",
                            "description": "Seconds since this price was fetched upstream."
                          }
                        },
                        "required": [
                          "symbol",
                          "usd",
                          "age_seconds"
                        ]
                      }
                    }
                  },
                  "required": [
                    "object",
                    "configured",
                    "data"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "The market source is not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "price_list"
                      ]
                    },
                    "configured": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {},
                      "maxItems": 0
                    }
                  },
                  "required": [
                    "object",
                    "configured",
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/market/status": {
      "get": {
        "operationId": "marketStatus",
        "tags": [
          "Meta"
        ],
        "security": [],
        "summary": "Whether live pricing is available",
        "description": "Public. Lets a caller hide price-dependent UI rather than render an empty figure.",
        "responses": {
          "200": {
            "description": "Status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "market_status"
                      ]
                    },
                    "configured": {
                      "type": "boolean"
                    },
                    "base_url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "configured"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/customers": {
      "post": {
        "operationId": "createCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Create a customer",
        "description": "Idempotent on `reference_id`: re-posting an existing one returns 200 with the existing customer rather than creating a duplicate.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Recommended. Replays the stored response for a repeated key."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reference_id": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Your own user id."
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The customer already existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listCustomers",
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "reference_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Customers.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Customer"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/customers/{id}": {
      "get": {
        "operationId": "getCustomer",
        "tags": [
          "Customers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — also returned for another project's customer, so a 404 never confirms existence.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateCustomer",
        "tags": [
          "Customers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "email"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "disabled"
                    ]
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "description": "No fields to update, or validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/customers/{id}/balances": {
      "get": {
        "operationId": "getCustomerBalances",
        "tags": [
          "Balances"
        ],
        "summary": "A customer's balance per asset",
        "description": "The read most integrations want. `available` is spendable; `locked` is reserved against an in-flight payout.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Balances.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "balance_list"
                    },
                    "customer_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Balance"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wallets": {
      "post": {
        "operationId": "createWallet",
        "tags": [
          "Wallets"
        ],
        "summary": "Get or create a customer's deposit address on a chain",
        "description": "Idempotent BY CONSTRUCTION — there is one wallet per (customer, chain). Calling again returns the same address with 200. Deriving a second address would split the customer's deposits across addresses your UI does not show.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "chain"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "chain": {
                    "type": "string",
                    "enum": [
                      "ethereum",
                      "bsc",
                      "polygon",
                      "arbitrum",
                      "base",
                      "avalanche",
                      "tron",
                      "solana",
                      "ton",
                      "bitcoin",
                      "dogecoin",
                      "optimism",
                      "linea",
                      "zksync",
                      "scroll"
                    ]
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 120
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The wallet already existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wallet"
                }
              }
            }
          },
          "201": {
            "description": "Derived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wallet"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Customer not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`chain_not_enabled` — the chain is not enabled for your project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listWallets",
        "tags": [
          "Wallets"
        ],
        "summary": "Every wallet, each with its active deposit address",
        "description": "The active address is included on every row, so a table of deposit addresses is one request rather than one per wallet. A wallet whose address is still being derived, or all of whose addresses are retired, comes back without the `address` field.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "customer_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "chain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            }
          },
          {
            "name": "role",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "customer",
                "master",
                "gas_station"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Wallets.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Wallet"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wallets/{id}": {
      "get": {
        "operationId": "getWallet",
        "tags": [
          "Wallets"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The wallet, including its active address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wallet"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wallets/{id}/addresses": {
      "get": {
        "operationId": "listWalletAddresses",
        "tags": [
          "Wallets"
        ],
        "summary": "Every address this wallet has had",
        "description": "Includes retired addresses. They stay monitored forever, so funds sent to an old one are still credited and you may need to explain where they came from.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Addresses.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Address"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/wallets/{id}/balances": {
      "get": {
        "operationId": "getWalletBalances",
        "tags": [
          "Balances"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ledger",
                "chain",
                "both"
              ],
              "default": "ledger"
            },
            "description": "`chain` and `both` include the on-chain reading and a `drift` field, and are rate-limited harder."
          }
        ],
        "responses": {
          "200": {
            "description": "Balances.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "balance_list"
                    },
                    "wallet_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "source": {
                      "type": "string"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Balance"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/addresses/{address}": {
      "get": {
        "operationId": "lookupAddress",
        "tags": [
          "Wallets"
        ],
        "summary": "Reverse-lookup an address",
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "chain",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Address"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — also returned for another project's address, so a 404 never confirms that we custody it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/master_wallets": {
      "post": {
        "operationId": "createMasterWallet",
        "tags": [
          "Wallets"
        ],
        "summary": "Create or register your master wallet for a chain",
        "description": "Omit `address` and we generate and custody it. Supply `address` and it is watch-only: sweeps can target it, but nothing here can ever spend from it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain"
                ],
                "properties": {
                  "chain": {
                    "type": "string",
                    "enum": [
                      "ethereum",
                      "bsc",
                      "polygon",
                      "arbitrum",
                      "base",
                      "avalanche",
                      "tron",
                      "solana",
                      "ton",
                      "bitcoin",
                      "dogecoin",
                      "optimism",
                      "linea",
                      "zksync",
                      "scroll"
                    ]
                  },
                  "address": {
                    "type": "string",
                    "description": "Present ⇒ watch-only."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wallet"
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wallet"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`chain_not_enabled`, or the address is invalid for that chain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listMasterWallets",
        "tags": [
          "Wallets"
        ],
        "responses": {
          "200": {
            "description": "Master wallets.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Wallet"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/balances": {
      "get": {
        "operationId": "getTreasury",
        "tags": [
          "Balances"
        ],
        "summary": "Project-wide solvency per asset",
        "description": "`surplus` is assets minus liabilities. A negative value means more is owed to customers than is held — the single most important number this service produces.",
        "responses": {
          "200": {
            "description": "Treasury.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "treasury"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TreasuryLine"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/deposits": {
      "get": {
        "operationId": "listDeposits",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "customer_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "wallet_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "chain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "detected",
                "confirmed",
                "failed",
                "orphaned"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deposits.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Deposit"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/deposits/{id}": {
      "get": {
        "operationId": "getDeposit",
        "tags": [
          "Deposits"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deposit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/test/deposits": {
      "post": {
        "operationId": "simulateDeposit",
        "tags": [
          "Test mode"
        ],
        "summary": "Simulate a deposit to one of your test addresses",
        "description": "Writes an inbound transfer to the customer's address on `chain` exactly as the watcher would have: a `detected` movement with `deposit.detected`, then (with `confirm: true`, the default) the asset's real confirmation threshold, the ledger credit and `deposit.confirmed`. The customer's balance moves; a payout can be requested against it.\n\n`decimals` must be echoed, as on `POST /v1/payouts` — rehearse the mismatch here rather than in production.\n\n`confirm: false` leaves the deposit `detected` so you can rehearse a pending state, then drive it with `/confirm`, `/fail` or `/orphan`.\n\nTest wallet keys only (`403 test_mode_only` otherwise). Needs `wallets:create`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "chain",
                  "asset",
                  "decimals",
                  "amount"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Whose address receives it. They must already hold one on `chain` (POST /v1/wallets)."
                  },
                  "chain": {
                    "type": "string",
                    "enum": [
                      "ethereum",
                      "bsc",
                      "polygon",
                      "arbitrum",
                      "base",
                      "avalanche",
                      "tron",
                      "solana",
                      "ton",
                      "bitcoin",
                      "dogecoin",
                      "optimism",
                      "linea",
                      "zksync",
                      "scroll"
                    ]
                  },
                  "asset": {
                    "type": "string",
                    "examples": [
                      "BNB"
                    ],
                    "description": "A symbol from GET /v1/chains for this test project."
                  },
                  "decimals": {
                    "type": "integer",
                    "description": "Must equal the asset's configured decimals. See GET /v1/chains."
                  },
                  "amount": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{0,38}$",
                    "description": "Atomic units, as a string. No decimal point."
                  },
                  "confirm": {
                    "type": "boolean",
                    "default": true,
                    "description": "false = stop at `detected`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The deposit, as `GET /v1/deposits/{id}` would return it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "400": {
            "description": "`decimals_mismatch` or a malformed amount.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`test_mode_only` — this key is live. Or the key lacks `wallets:create`, or is not a `wallet` key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`wallet_not_found` — the customer has no address on that chain yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`unknown_asset`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/test/deposits/{id}/confirm": {
      "post": {
        "operationId": "confirmTestDeposit",
        "tags": [
          "Test mode"
        ],
        "summary": "A pending test deposit reaches finality and is credited",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deposit, now `confirmed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`test_mode_only`, or the key lacks `wallets:create`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`deposit_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`deposit_not_pending` — only a `detected` deposit can be confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/test/deposits/{id}/fail": {
      "post": {
        "operationId": "failTestDeposit",
        "tags": [
          "Test mode"
        ],
        "summary": "A pending test deposit turns out not to be money",
        "description": "Closes a `detected` deposit as `failed` and emits `deposit.failed`, as the watcher does for a reverted transaction.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deposit, now `failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`test_mode_only`, or the key lacks `wallets:create`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`deposit_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`deposit_not_pending`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/test/deposits/{id}/orphan": {
      "post": {
        "operationId": "orphanTestDeposit",
        "tags": [
          "Test mode"
        ],
        "summary": "A confirmed test deposit is reorged out",
        "description": "Reverses the credit of a `confirmed` deposit and emits `deposit.orphaned` — the event your integration must handle by reversing its own credit. This is the only way to rehearse a reorg; rehearse it.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deposit, now `orphaned`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deposit"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`test_mode_only`, or the key lacks `wallets:create`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`deposit_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`deposit_not_confirmed` — only a `confirmed` deposit can be orphaned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payouts": {
      "post": {
        "operationId": "createPayout",
        "tags": [
          "Payouts"
        ],
        "summary": "Send funds to an external address",
        "description": "Returns **202** — accepted, not sent. The transaction hash arrives via the `payout.submitted` and `payout.confirmed` webhooks.\n\n`Idempotency-Key` is REQUIRED: a retried POST without one can send funds twice.\n\n`decimals` must match the asset's configured scale. This is checked because it is where the BSC trap bites — `\"1000000\"` is 1 USDT on Tron and 0.000000000001 USDT on BSC.\n\nAbove your auto-approve threshold the payout enters `pending_approval` and waits for a human. That is a safety feature, not an error.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "REQUIRED. A retry without one can send funds twice, so the request is rejected outright."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain",
                  "asset",
                  "amount",
                  "decimals",
                  "to_address"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Whose balance is debited."
                  },
                  "chain": {
                    "type": "string",
                    "enum": [
                      "ethereum",
                      "bsc",
                      "polygon",
                      "arbitrum",
                      "base",
                      "avalanche",
                      "tron",
                      "solana",
                      "ton",
                      "bitcoin",
                      "dogecoin",
                      "optimism",
                      "linea",
                      "zksync",
                      "scroll"
                    ]
                  },
                  "asset": {
                    "type": "string",
                    "examples": [
                      "USDT"
                    ]
                  },
                  "decimals": {
                    "type": "integer",
                    "description": "Must equal the asset's configured decimals. See GET /v1/chains."
                  },
                  "amount": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{0,38}$",
                    "description": "Atomic units, as a string. No decimal point."
                  },
                  "to_address": {
                    "type": "string"
                  },
                  "memo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Required on TON."
                  },
                  "reference": {
                    "type": "string",
                    "description": "Your own reference."
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_required`, `decimals_mismatch`, or a malformed amount.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_funds` — retryable after a deposit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_key_reuse` or `idempotency_in_progress`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_address`, `self_payout_forbidden`, `destination_not_allowlisted`, `chain_not_enabled`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listPayouts",
        "tags": [
          "Payouts"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "customer_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "chain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payouts.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Payout"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payouts/{id}": {
      "get": {
        "operationId": "getPayout",
        "tags": [
          "Payouts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payout.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/payouts/{id}/cancel": {
      "post": {
        "operationId": "cancelPayout",
        "tags": [
          "Payouts"
        ],
        "summary": "Cancel a payout that has not been signed yet",
        "description": "Releases the reserved funds back to the customer's available balance. Once signing has begun the chain owns the transaction and cancellation is impossible.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Canceled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`payout_not_cancelable` — signing has already begun.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/support/tickets": {
      "get": {
        "operationId": "listTickets",
        "tags": [
          "Support"
        ],
        "summary": "Your support tickets",
        "description": "Only your own tickets — scoped to the project the API key belongs to. Ordered by most recent activity.",
        "responses": {
          "200": {
            "description": "Your tickets, newest activity first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tickets": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Ticket"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createTicket",
        "tags": [
          "Support"
        ],
        "summary": "Open a ticket",
        "description": "The opening message is required — a ticket with no message cannot be answered. Created as `open`, waiting on us.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject",
                  "message"
                ],
                "properties": {
                  "subject": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The ticket.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ticket"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/support/tickets/{publicId}": {
      "get": {
        "operationId": "getTicket",
        "tags": [
          "Support"
        ],
        "summary": "One ticket and its messages",
        "description": "Internal staff notes are never included. A ticket belonging to another project answers 404, not 403 — the existence of someone else's ticket is not disclosed.",
        "parameters": [
          {
            "name": "publicId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The ticket's `public_id`, not its short number."
          }
        ],
        "responses": {
          "200": {
            "description": "The ticket and its visible messages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticket": {
                      "$ref": "#/components/schemas/Ticket"
                    },
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TicketMessage"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such ticket on this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/support/tickets/{publicId}/messages": {
      "post": {
        "operationId": "replyToTicket",
        "tags": [
          "Support"
        ],
        "summary": "Reply on a ticket",
        "description": "Moves the ticket back to waiting-on-us. A resolved ticket reopens; a closed one is refused — open a new ticket instead.",
        "parameters": [
          {
            "name": "publicId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The ticket's `public_id`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "properties": {
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`ticket_closed` — this ticket is closed. Open a new one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such ticket on this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sweeps": {
      "post": {
        "operationId": "createSweep",
        "tags": [
          "Sweeps"
        ],
        "summary": "Force a sweep into your master wallet",
        "description": "Normally automatic. A sweep does NOT change customer balances — the customer still owns the coins; they merely move from a per-customer deposit address into your omnibus master wallet.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Recommended. Replays the stored response for a repeated key."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "wallet_id"
                ],
                "properties": {
                  "wallet_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "asset": {
                    "type": "string",
                    "description": "Omit to sweep every eligible asset."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sweep"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`signing_disabled` — signing is off globally, for your project, or for this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`sweep_in_flight` — one is already running for this wallet and asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listSweeps",
        "tags": [
          "Sweeps"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "wallet_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sweeps.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Sweep"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sweeps/{id}": {
      "get": {
        "operationId": "getSweep",
        "tags": [
          "Sweeps"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sweep.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sweep"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/invoices": {
      "post": {
        "operationId": "createInvoice",
        "tags": [
          "Invoices"
        ],
        "summary": "Create an invoice",
        "description": "Mints one payment address per chain you accept (identical on every EVM chain) and locks a rate per asset for the life of the invoice. The payer sends the quoted atomic amount to the address; the settler credits it once final.\n\n`price_amount` is integer MINOR units of `price_currency` (cents), as a string. Only USD is priced today.\n\nThe fee is frozen onto the invoice at creation (`fee_bps`, `fee_fixed_cents`, `fee_cap_cents`) from your plan and is charged to your operational balance when the payment credits — never taken out of the coins.\n\nA `reference_id` makes the create idempotent: a retry returns the FIRST invoice, addresses and rates included.\n\nLive invoices require an active, funded plan (see `GET /v1/billing`); test projects are never gated.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Recommended. Replays the stored response for a repeated key."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "price_amount"
                ],
                "properties": {
                  "price_amount": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{0,17}$",
                    "description": "Minor units (cents), as a string."
                  },
                  "price_currency": {
                    "type": "string",
                    "default": "USD"
                  },
                  "reference_id": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "Your order id. Unique per merchant; makes the create idempotent."
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 256
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "accept": {
                    "type": "array",
                    "description": "Restrict this invoice to a subset of the assets you accept.",
                    "items": {
                      "type": "object",
                      "required": [
                        "chain",
                        "asset"
                      ],
                      "properties": {
                        "chain": {
                          "type": "string",
                          "enum": [
                            "ethereum",
                            "bsc",
                            "polygon",
                            "arbitrum",
                            "base",
                            "avalanche",
                            "tron",
                            "solana",
                            "ton",
                            "bitcoin",
                            "dogecoin",
                            "optimism",
                            "linea",
                            "zksync",
                            "scroll"
                          ]
                        },
                        "asset": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "return_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "cancel_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "expires_in_seconds": {
                    "type": "integer",
                    "minimum": 300,
                    "maximum": 86400,
                    "default": 1800
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An invoice with this `reference_id` already existed; returned unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`merchant_not_found` — this key's project is not a merchant account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`plan_not_active`, `billing_balance_exhausted`, `no_payment_methods`, `currency_not_supported`, `invalid_price`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listInvoices",
        "tags": [
          "Invoices"
        ],
        "summary": "List invoices",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invoices, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/InvoiceSummary"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/invoices/{id}": {
      "get": {
        "operationId": "getInvoice",
        "tags": [
          "Invoices"
        ],
        "summary": "Read an invoice with its addresses and locked rates",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The invoice's public id."
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/invoices/{id}/cancel": {
      "post": {
        "operationId": "cancelInvoice",
        "tags": [
          "Invoices"
        ],
        "summary": "Withdraw an unpaid invoice",
        "description": "Closes the window early, for an order that was abandoned or replaced. Needs `invoices:write` rather than a payout scope, because cancelling moves no money.\n\n🔴 IT DOES NOT CLOSE THE PAYMENT ADDRESS. The address is an ordinary keyless customer wallet and stays watched forever, so a transfer arriving afterwards is still detected and still credited — surfaced as `late`, never voided.\n\nAn invoice that has already received money cannot be cancelled: reversing a credited payment is a refund, which needs a destination only the payer can give.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The invoice's public id."
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`invoice_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`invoice_closed` — already cancelled, expired or settled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invoice_has_payments` — money has already been received against it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/checkout/{token}": {
      "get": {
        "operationId": "getCheckout",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "summary": "What the payer's page renders",
        "description": "Unauthenticated. The `checkout_token` on the invoice (`ck_` + 43 base64url characters, returned as part of `checkout_url`) is the entire authorisation, so treat the link like a bearer credential: anyone holding it sees this document.\n\n🔴 THIS IS NOT AN INVOICE, and the difference is deliberate. It carries no `metadata`, no `reference_id`, no internal ids and NO FEE of any kind — not the bps, not the fixed, not the cap, not a null one. A checkout link goes to strangers; publishing the fee would hand a merchant's negotiated rate to anyone who ever bought from them.\n\nThere is no separate status endpoint: poll this one. The page decays its polling (3s for the first two minutes, then 8s, then 20s), pauses on a hidden tab and catches up on `visibilitychange`.\n\nEvery miss — a malformed token, an unknown one, a deleted invoice — is the same 404 with no distinguishing detail.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^ck_[A-Za-z0-9_-]{43}$"
            },
            "description": "The invoice's `checkout_token` — the `ck_…` segment of its `checkout_url`."
          }
        ],
        "responses": {
          "200": {
            "description": "The checkout document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — no checkout for this link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/checkout/{token}/select": {
      "post": {
        "operationId": "selectCheckoutMethod",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "summary": "Record which chain and asset the payer picked",
        "description": "🔴 THIS LOCKS NOTHING AND STARTS NO CLOCK, whatever the name suggests. The invoice's window begins at creation, and one address serves every EVM chain, so money arriving on any offered chain settles against the invoice no matter what was selected here.\n\nIt is a display preference: it lets the page show one amount instead of six, and lets the re-quoter prioritise the asset somebody is actually reading. Returns the same document as the GET.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^ck_[A-Za-z0-9_-]{43}$"
            },
            "description": "The invoice's `checkout_token` — the `ck_…` segment of its `checkout_url`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain",
                  "asset"
                ],
                "properties": {
                  "chain": {
                    "type": "string",
                    "enum": [
                      "ethereum",
                      "bsc",
                      "polygon",
                      "arbitrum",
                      "base",
                      "avalanche",
                      "tron",
                      "solana",
                      "ton",
                      "bitcoin",
                      "dogecoin",
                      "optimism",
                      "linea",
                      "zksync",
                      "scroll"
                    ]
                  },
                  "asset": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checkout document, with `selected` set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — no checkout for this link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`invoice_closed` — the invoice is paid, canceled, expired or refunded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_selection` — this invoice does not offer that chain and asset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/checkout/{token}/refund": {
      "post": {
        "operationId": "confirmCheckoutRefund",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "summary": "The payer says where an overpayment goes",
        "description": "Only for an invoice whose merchant's `overpayment_policy` is `refund` and whose `refund` block is `awaiting_address`. Nothing is sent until this call: the address that paid is a SUGGESTION, offered as a prefill only when `sender_class` is `personal`, because a great many payments arrive from an exchange's shared hot wallet and a refund sent there lands in the exchange's float, credits nobody, and cannot be undone.\n\n🔴 Wait for BOTH `refund.sender_class` and `refund.net` to be non-null before showing the form at all. Until then this route refuses with `refund_not_ready`. A null class means we are still working out who paid, and a payer must not choose a destination against advice that arrives a second later; a null `net` means we have not finished pricing the send, and the form promises a payer three lines — excess, network fee, what you receive — so accepting an address before the middle one exists has them agreeing to a figure nobody had worked out.\n\nThe address is validated for the refund's own chain through that chain's adapter — never a regex — and an address we ourselves custody is refused.\n\nIdempotent on a repeat of the SAME address once the refund is `pending`; a DIFFERENT address is refused, because a queued refund must not be redirectable.\n\nRate limited to 10/minute: it is the most abusable route on the surface.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^ck_[A-Za-z0-9_-]{43}$"
            },
            "description": "The invoice's `checkout_token` — the `ck_…` segment of its `checkout_url`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "address"
                ],
                "properties": {
                  "address": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "A wallet the payer controls, on the refund's chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checkout document, with the refund now `pending`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_body`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — no checkout for this link, or nothing to refund.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`already_sent` — the refund has gone out. `already_confirmed` — a different address is already queued. `refund_not_ready` — we have not finished working out who paid, or what it costs to send; poll the GET and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_address` — not valid for that chain, or an address this service custodies.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing": {
      "get": {
        "operationId": "getBilling",
        "tags": [
          "Billing"
        ],
        "summary": "Your plan and operational balance",
        "description": "Every fee on the rate card is drawn from a prepaid OPERATIONAL BALANCE in USD cents. You fund it by sending USDT or USDC (credited at par) or a chain's native coin (credited at spot) to one of your funding addresses (`GET /v1/billing/funding`).\n\n`live.ok` says whether live invoices are accepted right now; `activation` says how much more the balance needs before the plan you chose switches on. The balance stays yours: it is refundable on request.",
        "responses": {
          "200": {
            "description": "Plan and balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Billing"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`merchant_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/plan": {
      "post": {
        "operationId": "choosePlan",
        "tags": [
          "Billing"
        ],
        "summary": "Choose or change your plan",
        "description": "Activates immediately when the balance already covers the plan's onboarding fee (an upgrade pays only the difference) plus its operational balance; otherwise the plan is recorded as requested and activates on the funding credit that closes the gap. Enterprise is agreed with us and cannot be self-selected.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "drift",
                      "current",
                      "jetstream",
                      "stratos"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Plan and balance after the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Billing"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_plan`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`plan_negotiated`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/onboarding/skip": {
      "post": {
        "operationId": "skipOnboarding",
        "tags": [
          "Billing"
        ],
        "summary": "Dismiss the getting-started checklist",
        "description": "Hides the four-step panel on your console overview. It is a display preference and grants nothing: the steps after the first — issue an address, take a test payment, go live — were never gates, and the only thing standing between your account and a live payment is an active plan with a positive balance.\n\nRefused with `plan_not_active` until your plan is on, because until then the panel is the only thing explaining why live invoices are being declined. Idempotent: calling it twice does not move the recorded timestamp.",
        "responses": {
          "200": {
            "description": "Plan and balance, with `onboarding.skipped_at` now set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Billing"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`merchant_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`plan_not_active`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/funding": {
      "get": {
        "operationId": "listFundingAddresses",
        "tags": [
          "Billing"
        ],
        "summary": "Where to send money to top up",
        "description": "One address per chain, created on first call and stable forever. EVM chains on the MolnPay factory share one address; read the list rather than assuming. Send only the assets listed for that chain; anything else is not credited.",
        "responses": {
          "200": {
            "description": "Funding addresses.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FundingAddress"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/entries": {
      "get": {
        "operationId": "listBillingEntries",
        "tags": [
          "Billing"
        ],
        "summary": "Your billing ledger, line by line",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "funding",
                "onboarding_fee",
                "payment_fee",
                "address_fee",
                "payout_fee",
                "withdrawal_fee",
                "monthly_minimum",
                "refund",
                "adjustment",
                "conversion_fee"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Entries, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/BillingEntry"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversion/rules": {
      "get": {
        "operationId": "listConversionRules",
        "tags": [
          "Conversions"
        ],
        "summary": "Your auto-conversion rule per network",
        "description": "One entry per network enabled for your project: whether it can convert at all (and why not), whether you switched it on, the target coin, the coins you keep as received, and the target choices — stablecoins first, then every other coin listed on that network.\n\nConversion happens on chain, from the treasury, after a payment is credited and swept: a payment is never delayed or refused because of it, and a conversion that cannot be executed leaves the payment in the coin it arrived in.",
        "responses": {
          "200": {
            "description": "Rules, one per enabled network.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ConversionRule"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`merchant_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversion/rules/{chain}": {
      "put": {
        "operationId": "setConversionRule",
        "tags": [
          "Conversions"
        ],
        "summary": "Switch auto conversion on a network on or off, and set its target and keep list",
        "description": "Takes effect on the NEXT payment; rows already queued keep the target they were given. Refused with `conversion_unsupported_chain` on a network that cannot convert, `invalid_target` when the target is not listed on the network, and `invalid_keep_asset` when a keep entry is not.",
        "parameters": [
          {
            "name": "chain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled",
                  "target_asset"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "target_asset": {
                    "type": "string"
                  },
                  "keep_assets": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rule as stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversionRule"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`chain_not_found` or `merchant_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`conversion_unsupported_chain`, `invalid_target`, `invalid_keep_asset`, `too_many_keep_assets`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversions": {
      "get": {
        "operationId": "listConversions",
        "tags": [
          "Conversions"
        ],
        "summary": "Every credited payment and what auto conversion did with it, plus your manual conversions",
        "description": "One row per payment, written when the payment is credited: `converted` with the executed rate and this payment's share of the swap's measured output, `skipped` with the reason (the rule was off, the coin is kept, it already was the target…), `queued` while it waits, or `failed` when every attempt reverted — which leaves the payment in the coin it arrived in. Manual conversions (`kind: manual`) appear here too, one row per customer balance they drew on.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "converting",
                "converted",
                "skipped",
                "failed"
              ]
            }
          },
          {
            "name": "chain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            }
          },
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "manual"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Conversions, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Conversion"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createConversion",
        "tags": [
          "Conversions"
        ],
        "summary": "Convert part of your balance now",
        "description": "The same on-chain pipeline auto conversion uses, entered by hand: the amount is drawn from the customer balances that hold the coin, sized against what your treasury holds free of everything already in flight, and the swap is quoted and planned at once. Quote first with `POST /v1/conversions/quote`.\n\nRefused with 402 `insufficient_funds` when the treasury on that network does not hold the amount (coins still in deposit addresses are swept first; `available` on the quote says what can go now), 422 `conversion_unsupported_chain` on a network with no route, 422 `conversion_test_mode` on a test-mode project, 503 `conversion_unavailable` when conversion is not configured.\n\nReturns 202 with the rows and, when it could be planned immediately, the batch. Follow the batch on `GET /v1/conversions/batches/{id}`; `conversion.completed` fires per row when it settles. Your plan's swap rate is charged on settlement.\n\n`Idempotency-Key` is REQUIRED: a retry with the same key replays the first answer rather than converting twice.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "REQUIRED. A retry without one can send funds twice, so the request is rejected outright."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain",
                  "from_asset",
                  "to_asset",
                  "amount"
                ],
                "properties": {
                  "chain": {
                    "type": "string",
                    "enum": [
                      "ethereum",
                      "bsc",
                      "polygon",
                      "arbitrum",
                      "base",
                      "avalanche",
                      "tron",
                      "solana",
                      "ton",
                      "bitcoin",
                      "dogecoin",
                      "optimism",
                      "linea",
                      "zksync",
                      "scroll"
                    ]
                  },
                  "from_asset": {
                    "type": "string"
                  },
                  "to_asset": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "string",
                    "pattern": "^[0-9]+$",
                    "description": "Atomic units of `from_asset`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted: the rows, and the batch when planned at once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversionRequest"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "`insufficient_funds`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`conversion_unsupported_chain`, `conversion_test_mode`, `unknown_asset`, `same_asset`, `invalid_amount`, `no_master_wallet`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`conversion_unavailable` — conversion is not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversions/quote": {
      "post": {
        "operationId": "quoteConversion",
        "tags": [
          "Conversions"
        ],
        "summary": "Price a manual conversion before committing",
        "description": "Read-only. One route quote, the estimated gas, the fee your plan would charge, and how much of the coin your treasury can convert right now. Quotes are not held: the batch quotes again when it is planned, and the swap books what the chain actually delivers.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain",
                  "from_asset",
                  "to_asset",
                  "amount"
                ],
                "properties": {
                  "chain": {
                    "type": "string",
                    "enum": [
                      "ethereum",
                      "bsc",
                      "polygon",
                      "arbitrum",
                      "base",
                      "avalanche",
                      "tron",
                      "solana",
                      "ton",
                      "bitcoin",
                      "dogecoin",
                      "optimism",
                      "linea",
                      "zksync",
                      "scroll"
                    ]
                  },
                  "from_asset": {
                    "type": "string"
                  },
                  "to_asset": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "string",
                    "pattern": "^[0-9]+$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversionQuote"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`conversion_unsupported_chain`, `conversion_test_mode`, `unknown_asset`, `same_asset`, `invalid_amount`, `no_master_wallet`, `no_route`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`conversion_unavailable` — conversion is not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversions/batches/{id}": {
      "get": {
        "operationId": "getConversionBatch",
        "tags": [
          "Conversions"
        ],
        "summary": "One on-chain swap, with its transactions",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversionBatch"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`conversion_batch_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversions/{id}": {
      "get": {
        "operationId": "getConversion",
        "tags": [
          "Conversions"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversion.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversion"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`conversion_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "tags": [
          "Webhooks"
        ],
        "summary": "Your endpoints",
        "description": "At most 16 per project, so this list is never paginated. `event_catalog` is every event type that exists — the values `event_types` may contain.",
        "responses": {
          "200": {
            "description": "Endpoints, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookEndpoint"
                          }
                        },
                        "event_catalog": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "customer.created",
                              "wallet.created",
                              "deposit.detected",
                              "deposit.confirmed",
                              "deposit.failed",
                              "deposit.orphaned",
                              "sweep.started",
                              "sweep.confirmed",
                              "sweep.failed",
                              "payout.created",
                              "payout.approved",
                              "payout.rejected",
                              "payout.submitted",
                              "payout.confirmed",
                              "payout.failed",
                              "payout.canceled",
                              "balance.transferred",
                              "balance.transfer_reversed",
                              "conversion.completed",
                              "conversion.failed",
                              "invoice.created",
                              "invoice.pending",
                              "invoice.paid",
                              "invoice.underpaid",
                              "invoice.overpaid",
                              "invoice.expired",
                              "invoice.canceled",
                              "invoice.refunded",
                              "endpoint.test"
                            ]
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Register an endpoint",
        "description": "The response carries the endpoint's signing `secret` — **the only time it is ever shown**. Store it before doing anything else; no endpoint can return it afterwards, and a lost secret is answered by `rotate_secret`.\n\nThe URL is checked before the endpoint exists (QUICKSTART §9.5): https only, port 443 or 8443, no credentials, resolving to a public address. It is checked again on every send.\n\nAn empty `event_types` subscribes to every event, including types added later — always keep a default branch in your handler.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Recommended. Replays the stored response for a repeated key."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "event_types": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "customer.created",
                        "wallet.created",
                        "deposit.detected",
                        "deposit.confirmed",
                        "deposit.failed",
                        "deposit.orphaned",
                        "sweep.started",
                        "sweep.confirmed",
                        "sweep.failed",
                        "payout.created",
                        "payout.approved",
                        "payout.rejected",
                        "payout.submitted",
                        "payout.confirmed",
                        "payout.failed",
                        "payout.canceled",
                        "balance.transferred",
                        "balance.transfer_reversed",
                        "conversion.completed",
                        "conversion.failed",
                        "invoice.created",
                        "invoice.pending",
                        "invoice.paid",
                        "invoice.underpaid",
                        "invoice.overpaid",
                        "invoice.expired",
                        "invoice.canceled",
                        "invoice.refunded",
                        "endpoint.test"
                      ]
                    },
                    "default": []
                  },
                  "delivery_mode": {
                    "type": "string",
                    "enum": [
                      "unordered",
                      "ordered_per_resource"
                    ],
                    "default": "unordered"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered. `secret` appears here and nowhere else.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                }
              }
            }
          },
          "400": {
            "description": "`webhook_url_insecure`, `webhook_url_port`, `webhook_url_credentials`, `webhook_url_forbidden`, `webhook_url_unresolvable`, or an unknown event type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`webhook_endpoint_limit` — 16 endpoints already exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "operationId": "getWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint. Never includes the secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found — also for another project's endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Edit, pause, resume or re-enable",
        "description": "`status: paused` stops sends while recording every event as a `skipped` delivery, so nothing is lost. `status: active` on a `disabled` endpoint (auto-disabled, or a 410 from your side) re-enables it and resets the strike count — then replay the gap with `POST /v1/webhook_deliveries/replay`. A changed URL is re-checked like a new one.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "event_types": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "customer.created",
                        "wallet.created",
                        "deposit.detected",
                        "deposit.confirmed",
                        "deposit.failed",
                        "deposit.orphaned",
                        "sweep.started",
                        "sweep.confirmed",
                        "sweep.failed",
                        "payout.created",
                        "payout.approved",
                        "payout.rejected",
                        "payout.submitted",
                        "payout.confirmed",
                        "payout.failed",
                        "payout.canceled",
                        "balance.transferred",
                        "balance.transfer_reversed",
                        "conversion.completed",
                        "conversion.failed",
                        "invoice.created",
                        "invoice.pending",
                        "invoice.paid",
                        "invoice.underpaid",
                        "invoice.overpaid",
                        "invoice.expired",
                        "invoice.canceled",
                        "invoice.refunded",
                        "endpoint.test"
                      ]
                    }
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ]
                  },
                  "delivery_mode": {
                    "type": "string",
                    "enum": [
                      "unordered",
                      "ordered_per_resource"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "description": "No fields to update, a bad URL, or an unknown event type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete an endpoint and its delivery history",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/rotate_secret": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "tags": [
          "Webhooks"
        ],
        "summary": "Issue a new signing secret",
        "description": "Both secrets sign every delivery until `overlap_until` (24 h by default, up to 168): the `MolnPay-Signature` header carries two `v1` values, and one match passes. Switch your receiver whenever you like inside the window. The new secret is shown only in this response.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "overlap_hours": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 168,
                    "default": 24
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Rotated. `secret` is the new one; `overlap_until` says how long the old one still verifies.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "operationId": "testWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send an `endpoint.test` event to this endpoint only",
        "description": "Goes through the real pipeline — signed, SSRF-checked, retried — so a green delivery here proves the receiver. Returns 202: watch the delivery at `GET /v1/webhook_deliveries/{delivery_id}`. Five per minute.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "event_id",
                    "delivery_id"
                  ],
                  "properties": {
                    "event_id": {
                      "type": "string"
                    },
                    "delivery_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`endpoint_not_active` — a paused or disabled endpoint would only record a `skipped` delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhook_deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "tags": [
          "Webhooks"
        ],
        "summary": "The delivery log",
        "description": "One row per attempt series: a delivery is retried in place (`attempt` climbs) and a replay is a NEW row with `replay_of`. Newest first. Filter by endpoint, event or status; `status=skipped` for one endpoint is exactly the gap an outage left.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "endpoint_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "succeeded",
                "failed",
                "exhausted",
                "skipped",
                "canceled"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookDelivery"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhook_deliveries/{id}": {
      "get": {
        "operationId": "getWebhookDelivery",
        "tags": [
          "Webhooks"
        ],
        "summary": "One delivery, with the receiver's response body",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`whd_…`"
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery. `response_body` is what your endpoint answered, up to 2 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/WebhookDelivery"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "response_body": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhook_deliveries/{id}/replay": {
      "post": {
        "operationId": "replayWebhookDelivery",
        "tags": [
          "Webhooks"
        ],
        "summary": "Re-send one delivery",
        "description": "Mints a new delivery (`replay_of` points back) for the SAME event — the receiver sees the same `id`, so an idempotent handler is unaffected. Only `failed`, `exhausted` and `skipped` deliveries can be replayed. Ten per minute.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplayResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`delivery_not_replayable` — the delivery is pending or already succeeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhook_deliveries/replay": {
      "post": {
        "operationId": "replayWebhookDeliveriesBulk",
        "tags": [
          "Webhooks"
        ],
        "summary": "Replay everything an endpoint missed",
        "description": "The recovery after an outage or an auto-disable: fix the receiver, PATCH the endpoint back to `active`, then call this. Replays up to 100 `failed`/`exhausted`/`skipped` deliveries for the endpoint, oldest first; narrow with `status` and `since`, and call again for a larger gap. Two per minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint_id"
                ],
                "properties": {
                  "endpoint_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "failed",
                      "exhausted",
                      "skipped"
                    ]
                  },
                  "since": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Only deliveries created at or after this instant."
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued. Empty `replays` means nothing matched.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplayResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The endpoint is not yours.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api_keys": {
      "get": {
        "operationId": "listApiKeys",
        "tags": [
          "API keys"
        ],
        "summary": "Your keys",
        "description": "Never the secret — `key_id` and `last4` are all that is stored. `created_by: molnpay_staff` marks a key our support minted for you. `limit_per_kind` and `default_scopes` are what a create call is measured against.",
        "parameters": [
          {
            "name": "include_revoked",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ],
              "default": "0"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Keys, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ApiKey"
                          }
                        },
                        "limit_per_kind": {
                          "type": "integer"
                        },
                        "default_scopes": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`session_required` — called with an API key. Keys are managed by the signed-in owner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Mint a key",
        "description": "The response carries `token` — **the only time it is ever shown**. We store a peppered hash; nothing can return it later. A lost key is rolled, never recovered.\n\nThe key's mode follows the project: a live project mints `mp…`, a test project `mptest…`.\n\n`scopes` defaults to the kind's read+write set (see `default_scopes` on the list). `payouts:create` is never a default. `signing_enabled` is the third factor a payout key needs to move funds, on top of kind and scope.\n\n`ip_allowlist`: exact IPs or CIDR blocks, v4 or v6, up to 32. Empty means any source. The address judged is the one your server presents to us. Leave it empty on serverless platforms.\n\nFive enabled keys per kind.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "kind"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "How you will recognise it later."
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "checkout",
                      "payout",
                      "wallet"
                    ]
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Concrete scopes, `*`, or `<group>:*`."
                  },
                  "ip_allowlist": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 32,
                    "default": []
                  },
                  "signing_enabled": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Minted. `token` appears here and nowhere else.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyWithToken"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed — an unknown scope grant, or an entry that is not an IP or CIDR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`session_required` — called with an API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`api_key_limit` — five keys of this kind already exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api_keys/{id}": {
      "get": {
        "operationId": "getApiKey",
        "tags": [
          "API keys"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The key. Never the token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Rename, re-scope, pin to IPs, or toggle signing",
        "description": "Takes effect within a minute everywhere. An empty `ip_allowlist` clears the restriction.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "ip_allowlist": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 32
                  },
                  "signing_enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                }
              }
            }
          },
          "400": {
            "description": "No fields to update, or validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`api_key_revoked` — a revoked key cannot be edited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "revokeApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Revoke, immediately and permanently",
        "description": "Idempotent. The row stays, marked `revoked_at`, so the audit trail keeps its name.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/api_keys/{id}/roll": {
      "post": {
        "operationId": "rollApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Replace a key without an outage",
        "description": "Mints a new key with identical grants and gives the old one `grace_hours` (default 24, max 168) to live — both work until then, so deploy the new key before the old one dies. The new `token` is shown only in this response.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "grace_hours": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 168,
                    "default": 24
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Rolled.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiKeyWithToken"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "old_key_expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`api_key_revoked`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "operationId": "listEvents",
        "tags": [
          "Events"
        ],
        "summary": "Every event your project emitted, newest first",
        "description": "Ordered by `sequence`, the same number the webhook envelope carries — so reconciling \"did we miss one?\" is a comparison against this list. `type` may be repeated.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from the previous page's `next_cursor`."
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "customer.created",
                  "wallet.created",
                  "deposit.detected",
                  "deposit.confirmed",
                  "deposit.failed",
                  "deposit.orphaned",
                  "sweep.started",
                  "sweep.confirmed",
                  "sweep.failed",
                  "payout.created",
                  "payout.approved",
                  "payout.rejected",
                  "payout.submitted",
                  "payout.confirmed",
                  "payout.failed",
                  "payout.canceled",
                  "balance.transferred",
                  "balance.transfer_reversed",
                  "conversion.completed",
                  "conversion.failed",
                  "invoice.created",
                  "invoice.pending",
                  "invoice.paid",
                  "invoice.underpaid",
                  "invoice.overpaid",
                  "invoice.expired",
                  "invoice.canceled",
                  "invoice.refunded",
                  "endpoint.test"
                ]
              }
            },
            "style": "form",
            "explode": true
          },
          {
            "name": "resource_id",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "The deposit, payout, customer… the event is about."
          }
        ],
        "responses": {
          "200": {
            "description": "Events.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Event"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`invalid_query` — an unknown event type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/stream": {
      "get": {
        "operationId": "streamEvents",
        "tags": [
          "Events"
        ],
        "summary": "Your events as they happen (Server-Sent Events)",
        "description": "A `text/event-stream` for dashboards and point-of-sale screens; webhooks remain the delivery contract for your backend. Frames: `ready` once with `{cursor}`; `event` with `{id, type, created, livemode, scope, data}` — `scope: \"account\"` carries the same `data` as `GET /v1/events`, `scope: \"funding\"` is a deposit to one of your funding addresses (`GET /v1/billing/funding`), trimmed to the transfer; `billing` with `{id, kind, amount_cents, note, chain, asset, created}` for each billing-ledger line; `ping` every 25 s. Every `event`/`billing` frame has an SSE `id`: send it back as `Last-Event-ID` to resume (replay is limited to the last 10 minutes). Streams close after 30 minutes; reconnect. At most 10 open streams per project.",
        "parameters": [
          {
            "name": "Last-Event-ID",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "The last frame `id` you received — resume after it."
          }
        ],
        "responses": {
          "200": {
            "description": "The stream.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`, or `too_many_streams` — already at the per-project stream limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{id}": {
      "get": {
        "operationId": "getEvent",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`evt_…`"
          }
        ],
        "responses": {
          "200": {
            "description": "The event, `data` included.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{id}/deliveries": {
      "get": {
        "operationId": "listEventDeliveries",
        "tags": [
          "Events"
        ],
        "summary": "Where each send of this event stood",
        "description": "One row per endpoint per (re)send. Not paginated — an event has a handful.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookDelivery"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, its IP allowlist rejected the source, or signing is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit or quota exceeded. Honour `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable. Retryable with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "customer.created": {
      "post": {
        "operationId": "onCustomerCreated",
        "summary": "Sent when a customer is created.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "customer.created"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Customer"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "wallet.created": {
      "post": {
        "operationId": "onWalletCreated",
        "summary": "Sent when a deposit address is derived.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "wallet.created"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Wallet"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "deposit.detected": {
      "post": {
        "operationId": "onDepositDetected",
        "summary": "Sent when an inbound transfer is seen on chain. NOT yet credited.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "deposit.detected"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Deposit"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "deposit.confirmed": {
      "post": {
        "operationId": "onDepositConfirmed",
        "summary": "Sent when a deposit passes its confirmation threshold and is credited. This is money.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "deposit.confirmed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Deposit"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "deposit.failed": {
      "post": {
        "operationId": "onDepositFailed",
        "summary": "Sent when an inbound transfer is dropped or reverted before confirming.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "deposit.failed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Deposit"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "deposit.orphaned": {
      "post": {
        "operationId": "onDepositOrphaned",
        "summary": "Sent when a CONFIRMED deposit is reorged out and reversed. You must reverse your own credit.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "deposit.orphaned"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Deposit"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "sweep.started": {
      "post": {
        "operationId": "onSweepStarted",
        "summary": "Sent when a consolidation transaction is broadcast.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "sweep.started"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Sweep"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "sweep.confirmed": {
      "post": {
        "operationId": "onSweepConfirmed",
        "summary": "Sent when funds arrive in your master wallet. Customer balances are unchanged.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "sweep.confirmed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Sweep"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "sweep.failed": {
      "post": {
        "operationId": "onSweepFailed",
        "summary": "Sent when a consolidation fails.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "sweep.failed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Sweep"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "payout.created": {
      "post": {
        "operationId": "onPayoutCreated",
        "summary": "Sent when a payout is accepted.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payout.created"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "payout.approved": {
      "post": {
        "operationId": "onPayoutApproved",
        "summary": "Sent when a payout clears human review.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payout.approved"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "payout.rejected": {
      "post": {
        "operationId": "onPayoutRejected",
        "summary": "Sent when a payout is rejected and its reserved funds are released.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payout.rejected"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "payout.submitted": {
      "post": {
        "operationId": "onPayoutSubmitted",
        "summary": "Sent when a payout transaction is broadcast.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payout.submitted"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "payout.confirmed": {
      "post": {
        "operationId": "onPayoutConfirmed",
        "summary": "Sent when a payout confirms on chain.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payout.confirmed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "payout.failed": {
      "post": {
        "operationId": "onPayoutFailed",
        "summary": "Sent when a payout fails and its funds are released.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payout.failed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "payout.canceled": {
      "post": {
        "operationId": "onPayoutCanceled",
        "summary": "Sent when a payout is canceled before signing.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payout.canceled"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "balance.transferred": {
      "post": {
        "operationId": "onBalanceTransferred",
        "summary": "Sent when staff moved a settled balance between two merchants (or to the house) with no chain transaction. Both sides receive it; `data.direction` says which side you are on.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "balance.transferred"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "balance.transfer_reversed": {
      "post": {
        "operationId": "onBalanceTransfer_reversed",
        "summary": "Sent when a `balance.transferred` was undone by a new, opposite posting.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "balance.transfer_reversed"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "conversion.completed": {
      "post": {
        "operationId": "onConversionCompleted",
        "summary": "Sent when a credited payment has been auto-converted on chain. `data.amount_out` is this payment's share of the measured output.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "conversion.completed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Conversion"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "conversion.failed": {
      "post": {
        "operationId": "onConversionFailed",
        "summary": "Sent when auto conversion of a payment gave up after every attempt. The payment stays credited in the coin it arrived in.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "conversion.failed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Conversion"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.created": {
      "post": {
        "operationId": "onInvoiceCreated",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.created"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.pending": {
      "post": {
        "operationId": "onInvoicePending",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.pending"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.paid": {
      "post": {
        "operationId": "onInvoicePaid",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.paid"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.underpaid": {
      "post": {
        "operationId": "onInvoiceUnderpaid",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.underpaid"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.overpaid": {
      "post": {
        "operationId": "onInvoiceOverpaid",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.overpaid"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.expired": {
      "post": {
        "operationId": "onInvoiceExpired",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.expired"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.canceled": {
      "post": {
        "operationId": "onInvoiceCanceled",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.canceled"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "invoice.refunded": {
      "post": {
        "operationId": "onInvoiceRefunded",
        "summary": "Sent when the event occurs.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "invoice.refunded"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    },
    "endpoint.test": {
      "post": {
        "operationId": "onEndpointTest",
        "summary": "Sent when you trigger a test delivery.",
        "description": "Verify `MolnPay-Signature` over the RAW REQUEST BODY before trusting any field. Return 2xx within 10 seconds, then process asynchronously — a slow 200 is recorded as a failed delivery. Delivery is at-least-once and unordered: dedupe on `id`, order on `sequence`.",
        "parameters": [
          {
            "name": "MolnPay-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "`t=<unix seconds>,v1=<hex>[,v1=<hex>]`. Two `v1` values appear during a secret rotation; one match passes."
          },
          {
            "name": "MolnPay-Event-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Event-Type",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Delivery-Id",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "MolnPay-Attempt",
            "in": "header",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "MolnPay-Api-Version",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Equal to the event id — retries and replays of one event share it."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "endpoint.test"
                      },
                      "data": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx works."
          },
          "410": {
            "description": "Gone. Disables the endpoint IMMEDIATELY — use this to stop delivery permanently."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer mp<64 hex>` for live, `mptest<64 hex>` for test.\n\nThe token is the prefix plus 64 lowercase hex characters (32 CSPRNG bytes) and nothing else — no separators, no embedded fields — so nothing in it needs quoting, escaping or encoding.\nSend it in the `Authorization` header and nowhere else. Never put it in a query string: URLs reach access logs, proxy logs, browser history and `Referer` headers, and a key that lands in any of those is a leaked key.\nA key's KIND (`checkout`, `payout` or `wallet`) is a property of the key, not of the string: it is chosen at creation, returned by `GET /v1/ping`, and decides which routes the key may reach.\n\nThe full key is shown exactly once, at creation. No endpoint can return it afterwards.\nMoving funds additionally requires signing to be enabled globally, for your project, and for that key — so a read-only key is a supported configuration."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code",
          "type",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable code. Identical to `code`; kept as a top-level string for client compatibility."
          },
          "code": {
            "type": "string",
            "examples": [
              "insufficient_funds"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "authentication_error",
              "invalid_request_error",
              "rate_limit_error",
              "idempotency_error",
              "api_error"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable. Do not parse."
          },
          "param": {
            "type": "string",
            "description": "The offending field, when applicable."
          },
          "request_id": {
            "type": "string",
            "description": "Quote this when reporting a problem.",
            "examples": [
              "req_01J8..."
            ]
          }
        }
      },
      "Asset": {
        "type": "object",
        "required": [
          "chain",
          "symbol",
          "decimals"
        ],
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "symbol": {
            "type": "string",
            "examples": [
              "USDT"
            ]
          },
          "decimals": {
            "type": "integer",
            "description": "Authoritative scale for this asset ON THIS CHAIN. USDT and USDC are 18 on BSC and 6 everywhere else — never hardcode 6.",
            "examples": [
              6
            ]
          },
          "contract": {
            "type": [
              "string",
              "null"
            ],
            "description": "null for the chain's native coin."
          }
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "object",
          "id",
          "status",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "customer"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own id for this user. Unique per project; re-posting it returns the existing customer."
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Wallet": {
        "type": "object",
        "required": [
          "object",
          "id",
          "chain",
          "role",
          "status",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "wallet"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "role": {
            "type": "string",
            "enum": [
              "customer",
              "master",
              "gas_station"
            ]
          },
          "status": {
            "type": "string"
          },
          "external": {
            "type": "boolean",
            "description": "true when you custody it and we hold no key."
          },
          "account_kind": {
            "type": "string",
            "enum": [
              "eoa",
              "contract"
            ],
            "description": "`contract` = a smart account: a contract address with no private key, identical on every EVM chain we run the factory on, moved by our operator without ever holding gas. `eoa` = a keypair we hold."
          },
          "address": {
            "type": "string",
            "description": "The deposit address."
          },
          "memo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Destination tag. When non-null the sender MUST include it or the deposit cannot be attributed. TON only."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Address": {
        "type": "object",
        "required": [
          "object",
          "id",
          "address",
          "chain"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "address"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "wallet_id": {
            "type": "string",
            "format": "uuid"
          },
          "address": {
            "type": "string"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "is_primary": {
            "type": "boolean"
          },
          "retired_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Retired addresses stay monitored forever — funds sent to one are still credited."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "asset",
          "available",
          "available_decimal",
          "locked",
          "total"
        ],
        "properties": {
          "asset": {
            "$ref": "#/components/schemas/Asset"
          },
          "available": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Spendable balance, atomic units.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "available_decimal": {
            "type": "string",
            "examples": [
              "1.5"
            ]
          },
          "locked": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Reserved against an in-flight payout.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "locked_decimal": {
            "type": "string"
          },
          "total": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "available + locked.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "total_decimal": {
            "type": "string"
          },
          "onchain": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Live on-chain balance. Only for source=chain|both.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "onchain_decimal": {
            "type": "string"
          },
          "drift": {
            "type": "string",
            "description": "onchain − total. Non-zero is normal transiently (unconfirmed deposits, in-flight sweeps); a persistent non-zero is worth investigating."
          }
        }
      },
      "TreasuryLine": {
        "type": "object",
        "properties": {
          "asset": {
            "$ref": "#/components/schemas/Asset"
          },
          "deposits": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Held on customer deposit addresses.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "treasury": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Held in your master wallet.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "in_transit": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Broadcast but not yet confirmed.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "liability": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Total owed to customers (available + locked).",
            "examples": [
              "1500000000000000000"
            ]
          },
          "surplus": {
            "type": "string",
            "description": "assets − liabilities. NEGATIVE means more is owed to customers than is held. Alarm on this."
          }
        }
      },
      "Deposit": {
        "type": "object",
        "required": [
          "object",
          "id",
          "chain",
          "amount",
          "tx_hash",
          "status"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "deposit"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "wallet_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "asset": {
            "$ref": "#/components/schemas/Asset"
          },
          "amount": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Amount received, atomic units.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "amount_decimal": {
            "type": "string"
          },
          "tx_hash": {
            "type": "string"
          },
          "log_index": {
            "type": "integer",
            "description": "Distinguishes several transfers of the same asset within one transaction."
          },
          "from_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "confirmations": {
            "type": "integer"
          },
          "required_confirmations": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "detected",
              "confirmed",
              "failed",
              "orphaned"
            ],
            "description": "Only `confirmed` is money. `detected` is seen-on-chain but not credited. `orphaned` was confirmed then reorged out and reversed — you must reverse your own credit."
          },
          "block_time": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "explorer_url": {
            "type": "string"
          },
          "ledger_tx_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Payout": {
        "type": "object",
        "required": [
          "object",
          "id",
          "chain",
          "amount",
          "to_address",
          "status"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "payout"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "asset": {
            "type": "string",
            "examples": [
              "USDT"
            ]
          },
          "asset_id": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Amount to send, atomic units.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "fee_amount": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Fee charged to the customer, same asset.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "to_address": {
            "type": "string"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "pending_approval",
              "approved",
              "rejected",
              "broadcast",
              "confirmed",
              "failed",
              "canceled"
            ],
            "description": "`pending_approval` means it exceeded your auto-approve threshold and awaits a human. That is a safety feature, not an error."
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Populated once broadcast."
          },
          "amount_usd": {
            "type": [
              "string",
              "null"
            ],
            "description": "Report-only estimate."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Ticket": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The short number to quote when you contact us."
          },
          "public_id": {
            "type": "string",
            "format": "uuid",
            "description": "Use this in URLs."
          },
          "subject": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "waiting_on_merchant",
              "waiting_on_us",
              "resolved",
              "closed"
            ],
            "description": "`waiting_on_us` means we owe you a reply; `waiting_on_merchant` means we have replied and are waiting for you."
          },
          "priority": {
            "type": "string",
            "enum": [
              "low",
              "normal",
              "high",
              "urgent"
            ]
          },
          "last_message_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_author": {
            "type": "string",
            "enum": [
              "merchant",
              "staff"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "TicketMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "author_type": {
            "type": "string",
            "enum": [
              "merchant",
              "staff",
              "system"
            ]
          },
          "author_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Sweep": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "sweep"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "wallet_id": {
            "type": "string",
            "format": "uuid"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "funding",
              "funded",
              "sweeping",
              "confirming",
              "done",
              "cooldown",
              "needs_review",
              "failed"
            ]
          },
          "amount": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Amount swept.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "asset": {
            "type": [
              "object",
              "null"
            ],
            "description": "The asset that moved. Present so the amount can be rendered — an atomic integer without its decimals is unreadable, and guessing is wrong by orders of magnitude.",
            "properties": {
              "symbol": {
                "type": "string"
              },
              "decimals": {
                "type": "integer"
              },
              "contract": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "amount_decimal": {
            "type": [
              "string",
              "null"
            ],
            "description": "`amount` shifted by the asset's decimals. Null if the asset is unknown."
          },
          "skip_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "`uneconomic` means the gas cost exceeded the policy's share of the value. Leaving dust is intentional — it accumulates until worth moving."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ListEnvelope": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list"
          },
          "data": {
            "type": "array",
            "items": {}
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque. Pass as `cursor` for the next page. Do not construct or parse it."
          }
        }
      },
      "Ping": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "ping"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_slug": {
            "type": "string"
          },
          "key_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The key's PUBLIC display handle — its prefix plus the first 8 hex of its body (`mp3f9c1a77`). Safe to log. It is not the whole key and cannot be used to authenticate."
          },
          "env": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "live",
              "test",
              null
            ]
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "checkout",
              "payout",
              "wallet",
              null
            ],
            "description": "What this key may reach, before scopes narrow it further: `checkout` = invoices and hosted checkout, `payout` = money out, `wallet` = customers, deposit addresses, deposits, balances and sweeps. Fixed at creation; a route that refuses the kind answers `403 wrong_key_kind`."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "signing_enabled": {
            "type": "boolean",
            "description": "false means this key cannot move funds, whatever its scopes say. True only when ALL THREE of the service, the project and the key have signing on — the same condition `POST /v1/payouts` and `POST /v1/sweeps` enforce."
          },
          "signing_blocked_by": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "service",
                "project",
                "key"
              ]
            },
            "description": "Empty when `signing_enabled` is true. Otherwise names which level is off, so you know what to ask for: `service` = the operator's global kill switch, `project` = your tenant, `key` = this specific key."
          },
          "network": {
            "type": "string",
            "enum": [
              "mainnet",
              "testnet"
            ]
          },
          "enabled_chains": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "ethereum",
                "bsc",
                "polygon",
                "arbitrum",
                "base",
                "avalanche",
                "tron",
                "solana",
                "ton",
                "bitcoin",
                "dogecoin",
                "optimism",
                "linea",
                "zksync",
                "scroll"
              ]
            }
          }
        }
      },
      "CoinSummary": {
        "type": "object",
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ],
            "description": "With `group=symbol` (the default) this is the coin's BEST-RANKED deployment, not its only one — see `chains`. With `group=contract` it is the row's own network."
          },
          "chain_name": {
            "type": "string",
            "examples": [
              "BNB Chain"
            ]
          },
          "chains": {
            "type": "integer",
            "description": "How many networks carry this coin. Always 1 under `group=contract`. Greater than 1 means `chain`, `contract` and `decimals` describe ONE of several deployments — re-query with `group=contract` to get them all."
          },
          "symbol": {
            "type": "string",
            "examples": [
              "PYUSD"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The coin's full name where the catalogue has one, e.g. `PayPal USD`."
          },
          "contract": {
            "type": [
              "string",
              "null"
            ],
            "description": "null for a chain's native coin. Belongs to `chain` — under `group=symbol` that is one deployment of several when `chains` > 1."
          },
          "decimals": {
            "type": "integer",
            "description": "This deployment's scale, which is NOT a property of the ticker: USDT is 6 on Ethereum and 18 on BSC. Under `group=symbol` it is the representative deployment's."
          },
          "is_native": {
            "type": "boolean"
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A logo you may render directly. Third-party art on a public CDN — treat it as decoration and fall back to your own placeholder if it fails to load."
          },
          "deposits_enabled": {
            "type": "boolean",
            "description": "The coin's own deposit switch, before the chain's is folded in."
          },
          "payouts_enabled": {
            "type": "boolean",
            "description": "The coin's own payout switch, before the chain's is folded in."
          },
          "decimals_differ_from_default": {
            "type": "boolean",
            "description": "This deployment's scale deviates from the symbol's usual one (USDT/USDC on BSC are 18, not 6)."
          },
          "live": {
            "type": "boolean",
            "description": "Is the PLATFORM servicing it today? The chain's switches and the coin's, combined. `false` means no new deposit address and no quote — NOT that a transfer to an address you already hold would be ignored; money sent to an address we issued is still credited. On a TEST key this still describes the platform: use `GET /v1/chains` for what a test project may act on."
          }
        },
        "required": [
          "chain",
          "chain_name",
          "chains",
          "symbol",
          "decimals",
          "is_native",
          "live"
        ]
      },
      "CoinTotals": {
        "type": "object",
        "properties": {
          "coins": {
            "type": "integer",
            "description": "(chain, symbol) pairs — CONTRACTS. USDC on Ethereum and USDC on Base count twice."
          },
          "live": {
            "type": "integer",
            "description": "How many of those are usable today."
          },
          "symbols": {
            "type": "integer",
            "description": "Distinct tickers. The honest figure for `how many coins`."
          },
          "chains": {
            "type": "integer"
          },
          "chains_live": {
            "type": "integer"
          },
          "by_chain": {
            "type": "object",
            "description": "Per network: `{ coins, live }`. So a networks table can print each row's count without pulling the whole catalogue to count it.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "coins": {
                  "type": "integer"
                },
                "live": {
                  "type": "integer"
                }
              },
              "required": [
                "coins",
                "live"
              ]
            }
          }
        },
        "required": [
          "coins",
          "live",
          "symbols",
          "chains",
          "chains_live",
          "by_chain"
        ]
      },
      "ChainInfo": {
        "type": "object",
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "name": {
            "type": "string"
          },
          "network_type": {
            "type": "string",
            "examples": [
              "TRC20"
            ]
          },
          "chain_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "native_currency": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "symbol": {
                "type": "string"
              },
              "decimals": {
                "type": "integer"
              }
            }
          },
          "required_confirmations": {
            "type": "integer"
          },
          "explorer_url": {
            "type": "string"
          },
          "enabled_for_tenant": {
            "type": "boolean"
          },
          "account_kind": {
            "type": "string",
            "enum": [
              "contract",
              "eoa"
            ],
            "description": "What a NEW deposit address on this chain is. `contract` = a keyless smart account on the MolnPay factory (one address shared by every EVM chain listed with `contract`); `eoa` = a keypair held by the signer. Keypair chains are listed only when the operator has switched them on."
          },
          "status": {
            "type": "string",
            "enum": [
              "live",
              "paused",
              "coming_soon"
            ],
            "description": "Platform-wide. `paused` is temporary (an incident); `coming_soon` is listed so you can see it is on the way. A hidden chain is simply absent from this list."
          },
          "status_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "The operator's note on why the chain is not issuing, when it is not. Show it verbatim."
          },
          "deposits_enabled": {
            "type": "boolean",
            "description": "Chain-level: may `POST /v1/wallets` derive a NEW address here right now? The per-asset flag below is ANDed with this. An address you already hold is always returned, whatever this says."
          },
          "payouts_enabled": {
            "type": "boolean",
            "description": "Chain-level: may a NEW payout be requested on this chain right now?"
          },
          "supports_memo": {
            "type": "boolean",
            "description": "When true, deposits without the memo cannot be attributed."
          },
          "assets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "symbol": {
                  "type": "string"
                },
                "decimals": {
                  "type": "integer"
                },
                "contract": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "is_native": {
                  "type": "boolean"
                },
                "deposits_enabled": {
                  "type": "boolean",
                  "description": "May a NEW deposit address be issued, or a quote made, for this asset? Chain and asset switches combined."
                },
                "payouts_enabled": {
                  "type": "boolean",
                  "description": "May this asset be chosen for a NEW payout? Chain and asset switches combined."
                },
                "decimals_differ_from_default": {
                  "type": "boolean",
                  "description": "true where this asset's scale deviates from its usual one — currently USDT and USDC on BSC (18, not 6)."
                }
              }
            }
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "object",
          "type",
          "created",
          "livemode",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "ULID. STABLE across every retry AND every replay — dedupe on it.",
            "examples": [
              "evt_01K3QW9Z8Y4M7F2N6X0P"
            ]
          },
          "object": {
            "type": "string",
            "const": "event"
          },
          "type": {
            "type": "string",
            "enum": [
              "customer.created",
              "wallet.created",
              "deposit.detected",
              "deposit.confirmed",
              "deposit.failed",
              "deposit.orphaned",
              "sweep.started",
              "sweep.confirmed",
              "sweep.failed",
              "payout.created",
              "payout.approved",
              "payout.rejected",
              "payout.submitted",
              "payout.confirmed",
              "payout.failed",
              "payout.canceled",
              "balance.transferred",
              "balance.transfer_reversed",
              "conversion.completed",
              "conversion.failed",
              "invoice.created",
              "invoice.pending",
              "invoice.paid",
              "invoice.underpaid",
              "invoice.overpaid",
              "invoice.expired",
              "invoice.canceled",
              "invoice.refunded",
              "endpoint.test"
            ]
          },
          "api_version": {
            "type": "string",
            "examples": [
              "2026-08-01"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "livemode": {
            "type": "boolean"
          },
          "tenant": {
            "type": "string",
            "format": "uuid"
          },
          "sequence": {
            "type": "integer",
            "description": "Monotonic per project. Order on this, never on arrival order. Gaps are normal — compare, never count."
          },
          "data": {
            "type": "object",
            "additionalProperties": true
          },
          "attempt": {
            "type": "integer"
          },
          "delivery_id": {
            "type": "string"
          }
        }
      },
      "Event": {
        "type": "object",
        "required": [
          "object",
          "id",
          "type",
          "api_version",
          "created",
          "livemode",
          "sequence",
          "resource_id",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "event"
          },
          "id": {
            "type": "string",
            "examples": [
              "evt_01K3QW9Z8Y4M7F2N6X0P"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "customer.created",
              "wallet.created",
              "deposit.detected",
              "deposit.confirmed",
              "deposit.failed",
              "deposit.orphaned",
              "sweep.started",
              "sweep.confirmed",
              "sweep.failed",
              "payout.created",
              "payout.approved",
              "payout.rejected",
              "payout.submitted",
              "payout.confirmed",
              "payout.failed",
              "payout.canceled",
              "balance.transferred",
              "balance.transfer_reversed",
              "conversion.completed",
              "conversion.failed",
              "invoice.created",
              "invoice.pending",
              "invoice.paid",
              "invoice.underpaid",
              "invoice.overpaid",
              "invoice.expired",
              "invoice.canceled",
              "invoice.refunded",
              "endpoint.test"
            ]
          },
          "api_version": {
            "type": "string"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "livemode": {
            "type": "boolean"
          },
          "tenant": {
            "type": "string",
            "format": "uuid"
          },
          "sequence": {
            "type": "integer",
            "description": "Monotonic per project, with gaps. The list is ordered by it."
          },
          "resource_id": {
            "type": "string",
            "description": "The id of the resource in `data` (a deposit, a payout, an endpoint…)."
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "The resource, identical to its REST GET shape."
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "required": [
          "object",
          "id",
          "url",
          "event_types",
          "status",
          "delivery_mode",
          "consecutive_failures",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "webhook_endpoint"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "https only, port 443 or 8443, public host — see QUICKSTART §9.5."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "event_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "customer.created",
                "wallet.created",
                "deposit.detected",
                "deposit.confirmed",
                "deposit.failed",
                "deposit.orphaned",
                "sweep.started",
                "sweep.confirmed",
                "sweep.failed",
                "payout.created",
                "payout.approved",
                "payout.rejected",
                "payout.submitted",
                "payout.confirmed",
                "payout.failed",
                "payout.canceled",
                "balance.transferred",
                "balance.transfer_reversed",
                "conversion.completed",
                "conversion.failed",
                "invoice.created",
                "invoice.pending",
                "invoice.paid",
                "invoice.underpaid",
                "invoice.overpaid",
                "invoice.expired",
                "invoice.canceled",
                "invoice.refunded",
                "endpoint.test"
              ]
            },
            "description": "Which events this endpoint receives. EMPTY means every event type, including ones added later."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "disabled"
            ],
            "description": "`paused` is yours (nothing is sent, deliveries are recorded as `skipped`); `disabled` is ours — auto-disabled after 5 consecutive exhausted deliveries or on a 410. PATCH `status: active` re-enables either."
          },
          "delivery_mode": {
            "type": "string",
            "enum": [
              "unordered",
              "ordered_per_resource"
            ]
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Exhausted deliveries in a row. Reset by any success and by re-enabling."
          },
          "disabled_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "disabled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "secret_overlap_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "While set, deliveries carry two `v1` signatures — the old secret still verifies until this instant."
          },
          "last_success_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_failure_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEndpointWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          {
            "type": "object",
            "required": [
              "secret"
            ],
            "properties": {
              "secret": {
                "type": "string",
                "description": "`whsec_` + 43 base64url characters. Returned ONLY here and in the rotate response; no endpoint can read it back. Store it now.",
                "examples": [
                  "whsec_9kQ2…"
                ]
              },
              "overlap_until": {
                "type": "string",
                "format": "date-time",
                "description": "Rotate only: until when the previous secret also signs."
              }
            }
          }
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "object",
          "id",
          "event_id",
          "endpoint_id",
          "status",
          "attempt",
          "max_attempts",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "webhook_delivery"
          },
          "id": {
            "type": "string",
            "examples": [
              "whd_01K3QWA2X0Q4M7F2N6X0P8"
            ]
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "event_id": {
            "type": "string"
          },
          "endpoint_id": {
            "type": "string",
            "format": "uuid"
          },
          "event_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "endpoint_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed",
              "exhausted",
              "skipped",
              "canceled"
            ],
            "description": "`pending` is queued or between retries; `exhausted` ran out of attempts; `skipped` was recorded while the endpoint was paused or disabled and is the gap you replay; `failed` is terminal without retry (a 410, or the URL failed the send-time check)."
          },
          "attempt": {
            "type": "integer",
            "description": "Attempts made so far."
          },
          "max_attempts": {
            "type": "integer"
          },
          "next_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "response_status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The receiver's HTTP status on the last attempt."
          },
          "resolved_ip": {
            "type": [
              "string",
              "null"
            ],
            "description": "The address the last attempt connected to."
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error_detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the last attempt did not succeed, in our words."
          },
          "replay_of": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set on a replay: the delivery it re-sends. The EVENT id is unchanged."
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "required": [
          "object",
          "id",
          "key_id",
          "env",
          "kind",
          "name",
          "last4",
          "scopes",
          "ip_allowlist",
          "signing_enabled",
          "created_by",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "api_key"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key_id": {
            "type": "string",
            "description": "The public display handle: prefix + first 8 hex.",
            "examples": [
              "mp3f9c1a77"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "checkout",
              "payout",
              "wallet"
            ]
          },
          "name": {
            "type": "string"
          },
          "last4": {
            "type": "string",
            "description": "The token's last 4 hex, so `mp3f9c1a77…4e5a` can be recognised."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ip_allowlist": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Empty = any source IP."
          },
          "signing_enabled": {
            "type": "boolean"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "merchant",
              "molnpay_staff"
            ],
            "description": "`molnpay_staff` when our support minted this key for you."
          },
          "rotated_from": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "use_count": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKeyWithToken": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "required": [
              "token"
            ],
            "properties": {
              "token": {
                "type": "string",
                "description": "`mp` or `mptest` + 64 hex. Returned ONLY here; no endpoint can read it back. Store it now."
              }
            }
          }
        ]
      },
      "ReplayResult": {
        "type": "object",
        "required": [
          "replays"
        ],
        "properties": {
          "replays": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "replay_of",
                "id"
              ],
              "properties": {
                "replay_of": {
                  "type": "string",
                  "description": "The delivery that was re-sent."
                },
                "id": {
                  "type": "string",
                  "description": "The new delivery. Watch it at `GET /v1/webhook_deliveries/{id}`."
                }
              }
            }
          }
        }
      },
      "Invoice": {
        "type": "object",
        "required": [
          "object",
          "id",
          "price_amount",
          "price_currency",
          "status",
          "fee_bps",
          "expires_at",
          "addresses",
          "quotes"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "invoice"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Public id. Use it in URLs and webhooks."
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_amount": {
            "type": "string",
            "description": "Minor units of `price_currency`, as a string."
          },
          "price_currency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "underpaid",
              "paid",
              "overpaid",
              "expired",
              "late",
              "canceled",
              "refunded"
            ]
          },
          "fee_bps": {
            "type": "integer"
          },
          "fee_fixed_cents": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "fee_cap_cents": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]+$"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancel_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "received_amount": {
            "type": "string",
            "description": "Cumulative minor units credited so far."
          },
          "excess_amount": {
            "type": "string",
            "description": "Minor units received beyond the price, outside the ±0.5% tolerance band. `0` unless the invoice is `overpaid`. Stored rather than derived, so it stays correct after a refund unwinds part of it."
          },
          "checkout_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where to send the payer. Treat it as a bearer credential: the token in it is the entire authorisation for the checkout page, so it is not the same value as `id` and must not be logged where `id` would be."
          },
          "addresses": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "chain",
                "address"
              ],
              "properties": {
                "chain": {
                  "type": "string",
                  "enum": [
                    "ethereum",
                    "bsc",
                    "polygon",
                    "arbitrum",
                    "base",
                    "avalanche",
                    "tron",
                    "solana",
                    "ton",
                    "bitcoin",
                    "dogecoin",
                    "optimism",
                    "linea",
                    "zksync",
                    "scroll"
                  ]
                },
                "address": {
                  "type": "string"
                },
                "memo": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "quotes": {
            "type": "array",
            "description": "Exactly what the payer must send, per (chain, asset), at the locked rate.",
            "items": {
              "type": "object",
              "required": [
                "chain",
                "asset",
                "amount_atomic",
                "decimals",
                "rate"
              ],
              "properties": {
                "chain": {
                  "type": "string",
                  "enum": [
                    "ethereum",
                    "bsc",
                    "polygon",
                    "arbitrum",
                    "base",
                    "avalanche",
                    "tron",
                    "solana",
                    "ton",
                    "bitcoin",
                    "dogecoin",
                    "optimism",
                    "linea",
                    "zksync",
                    "scroll"
                  ]
                },
                "asset": {
                  "type": "string"
                },
                "contract": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "amount_atomic": {
                  "type": "string",
                  "pattern": "^(0|[1-9][0-9]{0,38})$",
                  "description": "What the payer must send.",
                  "examples": [
                    "1500000000000000000"
                  ]
                },
                "amount": {
                  "type": "string",
                  "description": "The same, human-readable. Derived; never compare against it."
                },
                "decimals": {
                  "type": "integer"
                },
                "rate": {
                  "type": "string",
                  "description": "Units of `price_currency` per whole unit of `asset`."
                },
                "rate_source": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "InvoiceSummary": {
        "type": "object",
        "required": [
          "object",
          "id",
          "price_amount",
          "price_currency",
          "status"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "invoice"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_amount": {
            "type": "string"
          },
          "price_currency": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "received_amount": {
            "type": "string"
          },
          "fee_bps": {
            "type": "integer"
          },
          "fee_fixed_cents": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "fee_cap_cents": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]+$"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Checkout": {
        "type": "object",
        "required": [
          "object",
          "status",
          "merchant",
          "currency",
          "price",
          "received",
          "remaining",
          "excess",
          "expires_at",
          "extended",
          "overpayment_policy",
          "options",
          "payments"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "checkout"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "underpaid",
              "paid",
              "overpaid",
              "expired",
              "late",
              "canceled",
              "refunded"
            ]
          },
          "merchant": {
            "type": "object",
            "required": [
              "name",
              "logo_url"
            ],
            "properties": {
              "name": {
                "type": "string",
                "description": "The merchant's display name."
              },
              "logo_url": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency": {
            "type": "string",
            "description": "The pricing currency. USD today."
          },
          "price": {
            "type": "string",
            "description": "Decimal string, 2 dp. What is owed."
          },
          "received": {
            "type": "string",
            "description": "Decimal string, 2 dp. Credited so far."
          },
          "remaining": {
            "type": "string",
            "description": "Decimal string, 2 dp. Zero inside the ±0.5% tolerance band — a payer 3¢ short on a $100 invoice is `paid` and owes nothing."
          },
          "excess": {
            "type": "string",
            "description": "Decimal string, 2 dp. Received beyond the price, outside the band."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "extended": {
            "type": "boolean",
            "description": "The window was extended to let an underpaying payer send the rest. Say so on screen — a timer that silently jumps back up reads as a bug."
          },
          "overpayment_policy": {
            "type": "string",
            "enum": [
              "keep",
              "refund"
            ],
            "description": "What happens to money beyond the price. State it BEFORE the payer sends, and never call a kept overpayment a refund."
          },
          "selected": {
            "type": [
              "object",
              "null"
            ],
            "description": "What the payer picked. A display preference — it locks nothing and starts no clock.",
            "required": [
              "chain",
              "asset"
            ],
            "properties": {
              "chain": {
                "type": "string",
                "enum": [
                  "ethereum",
                  "bsc",
                  "polygon",
                  "arbitrum",
                  "base",
                  "avalanche",
                  "tron",
                  "solana",
                  "ton",
                  "bitcoin",
                  "dogecoin",
                  "optimism",
                  "linea",
                  "zksync",
                  "scroll"
                ]
              },
              "asset": {
                "type": "string"
              }
            }
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckoutOption"
            }
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckoutPayment"
            }
          },
          "refund": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CheckoutRefund"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present only when a refund of an overpayment exists on this invoice."
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CheckoutOption": {
        "type": "object",
        "required": [
          "chain",
          "asset",
          "decimals",
          "address",
          "amount",
          "amount_atomic",
          "rate",
          "stable"
        ],
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "asset": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Display name for the asset."
          },
          "decimals": {
            "type": "integer",
            "description": "🔴 The decimals this amount was COMPUTED with. Render `amount_atomic` through this and nothing else — USDT/USDC on BSC are 18, not 6."
          },
          "address": {
            "type": "string",
            "description": "Where the payer sends. Identical across EVM chains — the URI is what names the network."
          },
          "memo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Required to attribute the payment on the chains that use one."
          },
          "amount": {
            "type": "string",
            "description": "Human-readable form of `amount_atomic`. Derived; never compare against it."
          },
          "amount_atomic": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "Exactly what the payer must send.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "rate": {
            "type": "string",
            "description": "Units of the pricing currency per whole unit of `asset`."
          },
          "stable": {
            "type": "boolean",
            "description": "A par asset (USDT/USDC). Quoted once for the window and never refreshed."
          },
          "refresh_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When this quote is replaced. NULL for a stable asset, which has no refresh."
          },
          "uri": {
            "type": [
              "string",
              "null"
            ],
            "description": "🔴 The payment URI for the QR — `bnb:` on BSC, `ethereum:<contract>@<chainId>/transfer?…` elsewhere on EVM, TIP-681 on Tron, BIP-21 on UTXO. NULL on Solana and TON, which have no scheme worth emitting; show the bare `address` there. Our EVM address is the same on every chain, so this string is the ONLY thing in the QR naming the network."
          }
        }
      },
      "CheckoutPayment": {
        "type": "object",
        "required": [
          "chain",
          "asset",
          "amount",
          "tx_hash",
          "confirmations",
          "required_confirmations",
          "status",
          "at"
        ],
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "In units of `asset`, human-readable."
          },
          "tx_hash": {
            "type": "string"
          },
          "explorer_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "confirmations": {
            "type": "integer"
          },
          "required_confirmations": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "failed"
            ],
            "description": "`pending` is seen on chain but not yet final. It is not money until `confirmed`."
          },
          "at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CheckoutRefund": {
        "type": "object",
        "required": [
          "status",
          "chain",
          "asset",
          "gross",
          "fee",
          "net",
          "sender_class"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "awaiting_address",
              "pending",
              "sent",
              "failed",
              "skipped"
            ],
            "description": "`awaiting_address` = the payer has not yet said where it goes. Nothing is sent until they do."
          },
          "skip_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "`uneconomical` when the network fee would have exceeded the refund. There is no form to show."
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "asset": {
            "type": "string"
          },
          "gross": {
            "type": "string",
            "description": "The excess, in units of `asset`."
          },
          "fee": {
            "type": [
              "string",
              "null"
            ],
            "description": "The network cost of returning it, taken OUT of the refund.\n\n🔴 NULL MEANS \"NOT PRICED YET\", NOT \"FREE\". Gas is quoted a few seconds after the overpayment and the quote can fail transiently. Show a working state on null; rendering a zero would promise the payer the whole excess and then hand them less."
          },
          "net": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the payer actually receives: `gross` minus `fee`. Null whenever `fee` is null."
          },
          "sender_class": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "personal",
              "exchange",
              "unknown",
              null
            ],
            "description": "What the payment looked like it came from. Only `personal` is ever prefilled — a refund into an exchange's shared hot wallet lands in the exchange's own float and credits nobody.\n\n🔴 NULL MEANS \"NOT WORKED OUT YET\", NOT `unknown`. A refund row exists from the moment of overpayment; classifying who paid takes a few seconds longer. Show a working state on null and poll — rendering the blank \"paste a wallet you control\" form would be asking the payer to answer a question we have not finished asking. `POST …/refund` refuses with `refund_not_ready` until this is non-null."
          },
          "suggested_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "🔴 Populated ONLY for `sender_class: personal`. Prefill this and nothing else; never fall back to an address off a transaction. Always null while `sender_class` is null."
          },
          "to_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where it is going. NULL until the payer confirms one."
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "explorer_url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Billing": {
        "type": "object",
        "required": [
          "object",
          "plan",
          "plan_name",
          "plan_status",
          "requested_plan_name",
          "balance_cents",
          "live",
          "onboarding",
          "fees",
          "plans"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "billing"
          },
          "plan": {
            "type": "string",
            "enum": [
              "drift",
              "current",
              "jetstream",
              "stratos"
            ]
          },
          "plan_name": {
            "type": "string"
          },
          "plan_status": {
            "type": "string",
            "enum": [
              "pending",
              "active"
            ],
            "description": "`pending` = chosen but not yet funded to the plan's operational balance."
          },
          "plan_activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "requested_plan": {
            "type": [
              "string",
              "null"
            ],
            "description": "A plan waiting on funding, if any. The stable identifier — match on this."
          },
          "requested_plan_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "That plan's display name, as the pricing page shows it. Print this; the key and the name are allowed to differ."
          },
          "balance_cents": {
            "type": "string",
            "pattern": "^-?[0-9]+$",
            "description": "Prepaid operational balance, USD cents, signed. At or below zero, live invoices are refused."
          },
          "onboarding_paid_cents": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "live": {
            "type": "object",
            "required": [
              "ok"
            ],
            "properties": {
              "ok": {
                "type": "boolean"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "plan_not_active",
                  "billing_balance_exhausted",
                  null
                ]
              }
            }
          },
          "onboarding": {
            "type": "object",
            "description": "The getting-started checklist. It is guidance, not a gate — `live.ok` above is the only thing that decides whether invoices are accepted.",
            "required": [
              "steps",
              "bypassed",
              "skipped_at",
              "can_skip",
              "remaining"
            ],
            "properties": {
              "steps": {
                "type": "array",
                "description": "🔴 ONLY `plan` IS RESOLVED FROM A FACT ON THIS ROUTE. The other three are reported `done: false` unless bypassed, because deciding them means counting addresses, deposits and invoices — reads a console makes anyway, and three joins we will not run to draw a tick. Derive them yourself and treat `bypassed` as the override.",
                "items": {
                  "type": "object",
                  "required": [
                    "step",
                    "done",
                    "bypassed",
                    "complete"
                  ],
                  "properties": {
                    "step": {
                      "type": "string",
                      "enum": [
                        "plan",
                        "address",
                        "payment",
                        "live"
                      ]
                    },
                    "done": {
                      "type": "boolean",
                      "description": "The underlying fact holds."
                    },
                    "bypassed": {
                      "type": "boolean",
                      "description": "MolnPay support marked it satisfied instead."
                    },
                    "complete": {
                      "type": "boolean",
                      "description": "`done || bypassed` — what to render."
                    }
                  }
                }
              },
              "bypassed": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "address",
                    "payment",
                    "live"
                  ]
                },
                "description": "Steps MolnPay support ticked off for this account."
              },
              "skipped_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the checklist was dismissed. Null while it still shows."
              },
              "can_skip": {
                "type": "boolean",
                "description": "Whether it may be dismissed yet — the plan has to be active first."
              },
              "remaining": {
                "type": "integer",
                "description": "Steps still outstanding once overrides are applied."
              }
            }
          },
          "activation": {
            "type": [
              "object",
              "null"
            ],
            "description": "What the balance needs before the pending/requested plan activates. Null when nothing is pending.",
            "required": [
              "plan",
              "plan_name",
              "onboarding_due_cents",
              "balance_required_cents",
              "shortfall_cents"
            ],
            "properties": {
              "plan": {
                "type": "string"
              },
              "plan_name": {
                "type": "string",
                "description": "The display name of the plan waiting to activate."
              },
              "onboarding_due_cents": {
                "type": "string",
                "pattern": "^[0-9]+$"
              },
              "balance_required_cents": {
                "type": "string",
                "pattern": "^[0-9]+$"
              },
              "shortfall_cents": {
                "type": "string",
                "pattern": "^[0-9]+$",
                "description": "Send at least this much (in USD terms) to activate."
              }
            }
          },
          "fees": {
            "type": "object",
            "description": "What a new invoice is charged today: your plan, with any negotiated override.",
            "properties": {
              "rate_bps": {
                "type": "integer"
              },
              "fixed_cents": {
                "type": "string",
                "pattern": "^[0-9]+$"
              },
              "cap_cents": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^[0-9]+$"
              },
              "address_fee_cents": {
                "type": "string",
                "pattern": "^[0-9]+$"
              },
              "min_monthly_cents": {
                "type": "string",
                "pattern": "^[0-9]+$"
              }
            }
          },
          "plans": {
            "type": "array",
            "description": "The rate card, for a plan picker.",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "onboarding_fee_cents": {
                  "type": "string",
                  "pattern": "^[0-9]+$"
                },
                "min_monthly_cents": {
                  "type": "string",
                  "pattern": "^[0-9]+$"
                },
                "operational_balance_cents": {
                  "type": "string",
                  "pattern": "^[0-9]+$"
                },
                "rate_bps": {
                  "type": "integer"
                },
                "fixed_cents": {
                  "type": "string",
                  "pattern": "^[0-9]+$"
                },
                "cap_cents": {
                  "type": "string",
                  "pattern": "^[0-9]+$"
                },
                "negotiated": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "FundingAddress": {
        "type": "object",
        "required": [
          "object",
          "chain",
          "address",
          "account_kind",
          "assets"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "funding_address"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "address": {
            "type": "string"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Required on TON."
          },
          "account_kind": {
            "type": "string",
            "enum": [
              "contract"
            ],
            "description": "Always `contract`: funding addresses are keyless smart accounts on the factory chains only, and every EVM chain listed shares one address."
          },
          "assets": {
            "type": "array",
            "description": "What may be sent here. `par` assets credit 1:1; others at spot when the deposit confirms.",
            "items": {
              "type": "object",
              "properties": {
                "symbol": {
                  "type": "string"
                },
                "decimals": {
                  "type": "integer"
                },
                "par": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "BillingEntry": {
        "type": "object",
        "required": [
          "object",
          "id",
          "kind",
          "amount_cents",
          "created_at",
          "transfer"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "billing_entry"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "funding",
              "onboarding_fee",
              "payment_fee",
              "address_fee",
              "payout_fee",
              "withdrawal_fee",
              "monthly_minimum",
              "refund",
              "adjustment",
              "conversion_fee"
            ]
          },
          "amount_cents": {
            "type": "string",
            "pattern": "^-?[0-9]+$",
            "description": "Signed. Funding is positive; every fee is negative."
          },
          "ref_type": {
            "type": "string"
          },
          "ref_id": {
            "type": "string"
          },
          "chain": {
            "type": [
              "string",
              "null"
            ]
          },
          "asset": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_atomic": {
            "type": [
              "string",
              "null"
            ],
            "description": "For `funding`: what arrived, atomic units."
          },
          "rate": {
            "type": [
              "string",
              "null"
            ],
            "description": "For `funding`: USD per whole unit at credit. `1.000000` at par."
          },
          "rate_source": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "transfer": {
            "type": [
              "object",
              "null"
            ],
            "description": "For `funding`: the on-chain transfer that paid this line. Null on every other kind.",
            "required": [
              "amount_decimal",
              "tx_hash",
              "explorer_url",
              "from_address",
              "confirmations",
              "required_confirmations",
              "status",
              "block_time"
            ],
            "properties": {
              "amount_decimal": {
                "type": "string"
              },
              "tx_hash": {
                "type": "string"
              },
              "explorer_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "from_address": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "confirmations": {
                "type": "integer"
              },
              "required_confirmations": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "detected",
                  "confirmed",
                  "failed",
                  "orphaned"
                ]
              },
              "block_time": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        }
      },
      "ConversionRule": {
        "type": "object",
        "required": [
          "object",
          "chain",
          "supported",
          "enabled",
          "target_asset",
          "keep_assets",
          "targets"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "conversion_rule"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "name": {
            "type": "string"
          },
          "supported": {
            "type": "boolean",
            "description": "Whether this network can auto-convert at all. `false` carries `unsupported_reason`; a PUT is refused with `conversion_unsupported_chain`."
          },
          "unsupported_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "enabled": {
            "type": "boolean",
            "description": "Off by default. Only the networks you switch on convert."
          },
          "target_asset": {
            "type": "string",
            "description": "The coin every converted payment on this network becomes. Defaults to your settlement asset; must be one of `targets`."
          },
          "keep_assets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Coins on this network left exactly as received. A payment in one of these is `skipped` with reason `kept`."
          },
          "targets": {
            "type": "array",
            "description": "What you may pick as the target, stablecoins first.",
            "items": {
              "type": "object",
              "properties": {
                "symbol": {
                  "type": "string"
                },
                "stable": {
                  "type": "boolean"
                },
                "is_native": {
                  "type": "boolean"
                },
                "contract": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Conversion": {
        "type": "object",
        "required": [
          "object",
          "id",
          "kind",
          "chain",
          "from_asset",
          "amount_in",
          "status",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "conversion"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "auto",
              "manual"
            ],
            "description": "`auto`: written when a payment was credited under a rule. `manual`: you asked for it (`POST /v1/conversions`)."
          },
          "payment_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The credited payment this row belongs to. One conversion per payment, ever. Null for a manual row."
          },
          "receipt_no": {
            "type": [
              "string",
              "null"
            ]
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "from_asset": {
            "type": "string",
            "description": "The coin the payment arrived in."
          },
          "to_asset": {
            "type": [
              "string",
              "null"
            ],
            "description": "The target at the time of the payment. Null when no rule applied."
          },
          "amount_in": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "The payment's net credit, atomic units.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "amount_in_decimal": {
            "type": "string"
          },
          "amount_out": {
            "type": [
              "string",
              "null"
            ],
            "description": "This payment's share of the batch's MEASURED output, atomic units. Null until converted."
          },
          "amount_out_decimal": {
            "type": [
              "string",
              "null"
            ]
          },
          "rate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Whole units of `to_asset` per whole unit of `from_asset`, as executed on chain."
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "converting",
              "converted",
              "skipped",
              "failed"
            ],
            "description": "`queued` waits for the treasury to hold the coin and for the group to clear the economics floor; `failed` means kept as received, never money lost."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "off",
              "kept",
              "already_target",
              "chain_unsupported",
              "test_mode",
              "awaiting_sweep",
              "below_minimum",
              "gas_ratio",
              "no_route",
              "swap_failed",
              "spent",
              null
            ],
            "description": "Why the row is not (or not yet) converted. Machine tokens."
          },
          "batch_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The on-chain swap this row was executed in, with other payments of the same coin and target."
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "explorer_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "settled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ConversionQuote": {
        "type": "object",
        "required": [
          "object",
          "chain",
          "from_asset",
          "to_asset",
          "amount_in",
          "expected_out",
          "min_out",
          "slippage_bps",
          "available"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "conversion_quote"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "from_asset": {
            "type": "string"
          },
          "to_asset": {
            "type": "string"
          },
          "amount_in": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "What you asked to convert, atomic units.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "amount_in_decimal": {
            "type": "string"
          },
          "expected_out": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "What the route expects to deliver, atomic units. The swap books what actually arrives, never this.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "expected_out_decimal": {
            "type": "string"
          },
          "min_out": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "The floor the router enforces; below it the swap reverts rather than fills.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "min_out_decimal": {
            "type": "string"
          },
          "slippage_bps": {
            "type": "integer",
            "description": "Basis points between expected and minimum."
          },
          "rate": {
            "type": "string",
            "description": "Whole units of `to_asset` per whole unit of `from_asset`, at the quote."
          },
          "amount_usd": {
            "type": "string",
            "description": "Report-only. `0.00` when no price is known."
          },
          "gas_usd": {
            "type": "string",
            "description": "Estimated gas for the approve (if any) and the swap, paid by the treasury."
          },
          "fee_cents": {
            "type": "integer",
            "description": "What your plan charges on settlement, from the operational balance."
          },
          "market": {
            "type": "string",
            "description": "The route Bitget would take."
          },
          "available": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "How much of `from_asset` your treasury can convert right now, net of everything already in flight.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "available_decimal": {
            "type": "string"
          },
          "needs_approval": {
            "type": "boolean",
            "description": "A token input needs an approve transaction before the swap: two transactions, two confirmations."
          }
        }
      },
      "ConversionBatch": {
        "type": "object",
        "required": [
          "object",
          "id",
          "chain",
          "from_asset",
          "to_asset",
          "amount_in",
          "status",
          "rows",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "conversion_batch"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "bsc",
              "polygon",
              "arbitrum",
              "base",
              "avalanche",
              "tron",
              "solana",
              "ton",
              "bitcoin",
              "dogecoin",
              "optimism",
              "linea",
              "zksync",
              "scroll"
            ]
          },
          "from_asset": {
            "type": "string"
          },
          "to_asset": {
            "type": "string"
          },
          "amount_in": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,38})$",
            "description": "The batch's input, atomic units.",
            "examples": [
              "1500000000000000000"
            ]
          },
          "amount_in_decimal": {
            "type": "string"
          },
          "amount_out": {
            "type": [
              "string",
              "null"
            ],
            "description": "MEASURED from the receipt once confirmed. Null before."
          },
          "amount_out_decimal": {
            "type": [
              "string",
              "null"
            ]
          },
          "expected_out": {
            "type": [
              "string",
              "null"
            ]
          },
          "expected_out_decimal": {
            "type": [
              "string",
              "null"
            ]
          },
          "min_out": {
            "type": [
              "string",
              "null"
            ]
          },
          "rate": {
            "type": [
              "string",
              "null"
            ],
            "description": "As executed."
          },
          "amount_usd": {
            "type": [
              "string",
              "null"
            ]
          },
          "fee_cents": {
            "type": "integer"
          },
          "market": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "approving",
              "approved",
              "swapping",
              "landed",
              "confirmed",
              "failed"
            ],
            "description": "`approving`/`approved`: the allowance transaction; `swapping`: the swap is planned or in flight; `landed`: confirmed, being measured; `confirmed`: settled; `failed`: the rows went back to the queue."
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "approve_tx_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "approve_explorer_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "swap_tx_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "swap_explorer_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "rows": {
            "type": "integer",
            "description": "How many conversion rows this batch carries."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "settled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ConversionRequest": {
        "type": "object",
        "required": [
          "object",
          "rows",
          "batch",
          "pending_reason"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "conversion_request"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Conversion"
            },
            "description": "The rows written for your request, one per customer balance the input was drawn from."
          },
          "batch": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ConversionBatch"
              },
              {
                "type": "null"
              }
            ],
            "description": "The swap, when it could be planned at once. Follow it on `GET /v1/conversions/batches/{id}`."
          },
          "pending_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "When `batch` is null: why not yet (`no_route`, `gas_ratio`, `below_minimum`…). The rows stay queued and the planner retries."
          }
        }
      }
    }
  }
}
