{
  "openapi": "3.1.0",
  "info": {
    "title": "OZAV API",
    "version": "2.0.0",
    "summary": "Canonical OZAV platform API (v2)",
    "description": "OZAV gives you three products behind one API: wallet infrastructure your users control,\ncompliance checks you act on, and a regulated platform that moves money for you. Use one of them,\nor all three under the same key.\n\n## Three products\n\n| | Wallet as a Service | Compliance as a Service | Money Movement |\n|---|---|---|---|\n| **What you build** | Wallets inside your product, for your users | Sanctions, PEP and risk checks inside your own flows | Accounts, FX, payouts, custody and cards for your customers |\n| **Who holds the keys** | Your users | - | OZAV |\n| **Who decides** | You | You, on OZAV's evidence | OZAV, as the regulated operator |\n| **Going live** | Accept the terms in the portal and add prepaid credit. No KYB of your company | Accept the terms in the portal and add prepaid credit. No KYB of your company | KYB of your company and a signed contract |\n| **Billing** | Prepaid, per call | Prepaid, per call | Monthly invoice |\n\nEach section of this reference opens with an **Overview**: what the product does, and the calls\nin the order you make them. **Yield** and **Agents** (beta) have sections of their own.\n\nThe machine-readable contract is at `https://ozav.io/openapi.yaml` (or `.json`). It is the file\nthis page renders, so the two never drift:\n\n```\nnpx openapi-typescript https://ozav.io/openapi.json -o ozav.d.ts\n```\n\n## Get access\n\n1. **Apply** at [ozav.io/caas/apply](https://ozav.io/caas/apply). No key and no sales call needed.\n2. **OZAV reviews the application.** A person reviews it, not an automated rule.\n3. **Your sandbox opens.** You get a portal invite and mint your own `ozav_sk_test_` keys under\n   *Developers -> API Keys*, scoped as you choose, up to ten per environment.\n4. **Go live.** Wallet as a Service and Compliance as a Service go live when an admin accepts the\n   terms in the portal and adds prepaid credit. Money Movement goes live after KYB of your company\n   and a signed contract; OZAV then issues your `ozav_sk_live_` keys.\n\n`GET /capabilities` returns, for the key you call it with, the products enabled for your\norganization, the key's scopes and what each feature needs.\n\n## Authentication\n\nAuthenticate every request with your secret key in the **`X-API-Key`**\nheader (or as an `Authorization: Bearer` token - the same key, either way).\n\n```\nX-API-Key: ozav_sk_test_5f8d…    # sandbox\nX-API-Key: ozav_sk_live_9a1c…    # production\n```\n\n- **The key prefix selects the environment.** `ozav_sk_test_` keys resolve\n  against the sandbox; `ozav_sk_live_` keys resolve against production. The\n  base URL is identical for both - there are no separate hostnames and no\n  code changes to promote from test to live.\n- **Two authorization axes.** Your organization holds **entitlements** (the\n  products enabled for you: `liquidity`, `compliance`, `customers`, `cards`,\n  `custody`, `self_custody`, `kyc_reliance`, `agents`). Each key additionally carries\n  **scopes** (e.g. `quotes:write`, `transfers:read`) that restrict what\n  that key may do. Effective permission is the intersection. A missing\n  product returns `403 ENTITLEMENT_REQUIRED` (\"talk to us\"); a missing\n  scope returns `403 INSUFFICIENT_SCOPE` (\"reissue the key with that\n  scope\").\n- **Getting keys.** Mint, list, rotate, and revoke keys under the API Keys\n  resource, or in the developer portal. A new key's secret is shown **once**\n  at creation - store it securely. Rotate with an overlap window so the old\n  secret keeps working while you cut over.\n- **Keep secrets server-side.** A live secret key can move money. Never ship\n  it in a browser, a mobile binary, or a public repository.\n\n## Sandbox & Environments\n\nThere is one base URL - `https://developers.ozav.io/v2` - for both\nenvironments; the key prefix does the routing.\n\nIt is `developers.`, not `api.`. `api.ozav.io` resolves and answers, but it\nis not this API: it returns `{\"error\":\"requested path is invalid\"}`, a shape\nthat appears nowhere in this document and carries no `request_id`. If you\nget an error whose body does not match the `Error` schema below, check the\nhostname before checking anything else.\n\n| | Test | Live |\n|---|---|---|\n| Key prefix | `ozav_sk_test_` | `ozav_sk_live_` |\n| Moves real money | No | Yes |\n| Data | Isolated sandbox | Production |\n\nA `test` key exercises customers, verifications, accounts, quotes,\ntransfers and screenings with no real settlement and no real bank-account\nopening. When you are ready, swap the key for its `live_` counterpart; your\ncode does not change.\n\n**Your data does not carry over, and this is the part that surprises people.**\nEvery resource belongs to exactly one environment. A customer created with a\n`test` key exists only in test; a customer created with a `live` key exists\nonly in live. **The ids are different, and an id from one environment is not\nfound in the other** - `GET /customers/{id}` with the wrong environment's key\nanswers `404 CUSTOMER_NOT_FOUND`, and `GET /customers` lists only that\nenvironment's records. A brand new `test` key therefore lists **zero**\ncustomers, even for an organization with many customers in production. That\nis not an error and not a permission problem: there is simply nothing in test\nyet.\n\nSo the first thing to do with a `test` key is **create your own test data**\nwith it: `POST /customers`, then accounts, wallets, quotes. Do not reuse\nproduction ids. When you go live, the same code runs against the `live` key\nand operates on the live records it creates there.\n\n## Idempotency\n\nMoney-moving and creation endpoints accept an **`Idempotency-Key`** header\nso a retry can never execute twice.\n\n- **Format:** 16-256 characters of `[A-Za-z0-9_-]`. Generate a fresh key\n  per logical operation (a UUID works well) and reuse it only when retrying\n  that exact operation.\n- **Required vs optional.** `POST /transfers` **requires** it - a transfer\n  without one is rejected. `POST /customers` and `POST /payout-destinations`\n  accept it (optional, recommended). Reads never need one.\n- **`POST /accounts` is the exception, and it does not read the header at\n  all.** Sending `Idempotency-Key` there is not an error and not a\n  protection: none of the mechanics below apply to it - no\n  `Idempotent-Replayed`, no `409 IDEMPOTENCY_KEY_REUSED` on a changed body,\n  no in-flight `202`. This paragraph exists because the sentence above says\n  \"creation endpoints\" and an integrator would reasonably include it.\n\n  What protects that endpoint instead is the **natural key**: an account is\n  unique per `(customer, currency, country, rail)`. Repeat the same request\n  and you get **`200`** with the account that already exists, where the\n  first call returned **`201`** - so the status code, not a header, is how\n  you tell a fresh account from an existing one. You do not need to generate\n  a key for it, and reusing one across different corridors will not confuse\n  it.\n- **`POST /accounts/{account_id}/payouts` does not read the header either.**\n  Its idempotency is the request itself: the same `reference` and `amount`\n  return the payout that already exists (see that route). Build `reference`\n  once per payout and keep it across retries.\n- **Replay.** A retry with the **same key and same body** replays the\n  original response verbatim - same status code - plus the header\n  **`Idempotent-Replayed: true`**, so you can tell a replay from a fresh\n  call.\n- **In flight.** If the first request is still executing when the retry\n  arrives, the retry returns **`202`** with `{\"status\":\"processing\"}` -\n  wait, then retry with the same key to receive the stored result.\n- **A `202` that does not resolve means the outcome is unknown, not\n  pending.** If the original request failed after the resource may already\n  have been written, the key stays claimed and every retry with it answers\n  `202` - there is no stored result coming. Do not loop. Reconcile with a\n  `GET` (list by `customer_id`, or read the id you were given) to find out\n  whether the resource exists, then continue with a **new** key if it does\n  not. A claimed key that outlives its request is recoverable; a duplicate\n  customer or payout destination is not.\n- **Conflicts.** The same key with a **different body** is a client bug and\n  returns **`409 IDEMPOTENCY_KEY_REUSED`** - it never overwrites the first\n  result. Use a new key when the request has genuinely changed.\n\nRetry idempotency-safe on `429` and `5xx` with the same key and exponential\nbackoff; treat a `4xx` other than `409` as terminal. Cap the retries: if\nthe response becomes a `202` that repeats, stop and reconcile as described\nabove rather than retrying indefinitely.\n\n## Pagination\n\nList endpoints are cursor-paginated and uniform.\n\n- **`limit`** - page size, `1`-`100`, default `20`.\n- **`starting_after`** - an opaque cursor; pass the previous page's\n  `next_cursor`.\n\nEvery list responds with the same envelope:\n\n```json\n{\n  \"data\": [ /* … */ ],\n  \"has_more\": true,\n  \"next_cursor\": \"b3phdl9jdXNfMDFK…\"\n}\n```\n\nLoop until `has_more` is `false`. Cursors are opaque - do not parse or\nconstruct them; a malformed cursor fails loud with `400 INVALID_CURSOR`\nrather than silently resetting to page one.\n\n## Rate limits\n\nOZAV enforces **90 requests per minute per organization**: every key, test and\nlive alike, shares one counter across all routes. The OZAV portal counts too\nwhile your team uses it to quote, send or follow a transfer: an open quote\nscreen refreshes about every 12 seconds (5 requests a minute), and an open\ntransfer page polls its status every 3 seconds for its first 3 minutes (20 a\nminute), then every 15 seconds. Payouts an agent platform makes under a\ngrant you gave it count against your organization as well: two requests\neach (a quote and a transfer), three when the payout waits for approval and\nis quoted again. When your organization is at its limit, those payouts fail\ntoo. One limit, no plan\ntiers. Exceeding it returns `429 RATE_LIMIT_EXCEEDED`. If OZAV cannot read the\ncounter, the request is refused with `503 RATE_LIMIT_UNAVAILABLE` (`type: api_error`)\ninstead: that is an OZAV-side failure, not your quota. Every counted response carries `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Reset`; read `Remaining` and slow\ndown before you hit the wall. A request that matches no route (`404`), or\nthat the route refuses before it runs because your key lacks the scope or\nthe product the route requires (`403`), does not count: the limit measures\nuse, not attempts. Once the route runs, the response counts whatever it\nis, a refusal included (a `422`, a `403` such as `COMPLIANCE_HOLD`, or an\n`ENTITLEMENT_REQUIRED` that depends on what the request names). The\nobservable rule: a response that carries `X-RateLimit-Remaining` was\ncounted, and one without it was not.\n\nDesign against the response too, and read its `code` first: a\n**`429 rate_limit_error`** is either a request to slow down or a cap with\na stated reset. `RATE_LIMIT_EXCEEDED` is the per-minute limit and clears\nwhen the window turns - `Retry-After` says when, and it is never more than\n60 seconds. A cap says so in `code` and `message` - for example\n`CREDENTIAL_ISSUANCE_LIMIT_REACHED` resets the next day, and retrying\nsooner cannot succeed. Otherwise wait the number of seconds in\n`Retry-After` when the header is present, and when it is not, back off\nexponentially starting at one second. Treat a `503` the same way.\n\nBefore repeating a write, check whether it takes an `Idempotency-Key`: a\nroute that documents one returns the stored result on a retry with the\nsame key (see Idempotency); a route that does not may already have\nexecuted, so reconcile with a `GET` before sending it again.\n\nCard activity raises webhooks (`card.authorized`, `card.declined`,\n`card.settled`, `card.refunded`). A hold that is released without settling\nraises none: on `GET /cards/{card_id}/authorizations` it shows as\n`status: reversed` (released or expired) or `declined` (refused by the card\nprocessor after OZAV approved it); in both the money is back on the balance.\nA hold is open while its `status` is `pending` or `authorized`. `settled` is\nfinal. `reversed` and `declined` end the hold, but a merchant can still\ncapture it late: the same row then turns `settled`, OZAV charges the amount\nto the balance again and `card.settled` fires. That row keeps its original\n`created_at`, so it can sit far back in the list. Poll that route only for cards that\nstill have an open hold, read a card again on its next card event, and keep\nall polling well inside your organization's 90 requests per minute. See\n`GET /cards/{card_id}/transactions` for how to read it incrementally.\n\n## Amounts & Currencies\n\n- **Amounts are decimal strings**, never floats: `\"1250.00\"`, `\"0.50\"`.\n  This preserves precision across currencies - the float `0.1` cannot\n  represent one tenth exactly, and money math must not drift.\n- **Precision.** Quote and balance amounts carry up to **6** fractional\n  digits; the amount you submit when creating a transfer accepts up to\n  **8**.\n- **Currency codes** are uppercase ISO 4217 (`BRL`, `USD`, `EUR`, `MXN`) or\n  stablecoin symbols (`USDC`, `USDT`). Codes are accepted case-insensitively\n  on input and always returned uppercase.\n- **Exchange conditions are a spread in integer basis points** - `150`\n  means 150 bps (1.50%), stored as an integer and divided by 10,000 in rate\n  math. Never a float or a percentage string. A quote's public\n  `exchange_conditions.spread_bps` is only the additional spread selected\n  by the client for that quote. OZAV cost, floor, and margin analytics stay\n  internal.\n\n## Errors\n\nEvery error returns the same JSON envelope alongside the HTTP status:\n\n```json\n{\n  \"error\": {\n    \"type\": \"invalid_request_error\",\n    \"code\": \"MISSING_FIELD\",\n    \"message\": \"email is required.\",\n    \"param\": \"email\",\n    \"request_id\": \"req_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"details\": [\n      { \"code\": \"MISSING_FIELD\", \"message\": \"email is required.\", \"param\": \"email\" }\n    ]\n  }\n}\n```\n\n- **`type`** is one of eight stable classes:\n\n  | `type` | Meaning | Typical status |\n  |---|---|---|\n  | `invalid_request_error` | Malformed or unfulfillable input - a missing field, a bad value, or a well-formed request that cannot be executed. | `400`, `422` |\n  | `authentication_error` | Missing, invalid, revoked, or expired key. | `401` |\n  | `permission_error` | Authenticated but not permitted - product not enabled, or the key lacks the scope. | `403` |\n  | `not_found_error` | Resource does not exist or is not visible to the caller. | `404` |\n  | `conflict_error` | Conflicts with existing state - e.g. an idempotency key reused with a new body, or a duplicate customer. | `409` |\n  | `rate_limit_error` | Too many requests - honor `Retry-After`. | `429` |\n  | `idempotency_error` | The idempotency layer could not be reached; retry the same request. | `503` |\n  | `api_error` | An unexpected server-side or dependency error; OZAV fails closed rather than guess. | `500`, `503` |\n\n- **`code`** is a stable, machine-readable string you can branch on\n  (`ENTITLEMENT_REQUIRED`, `INSUFFICIENT_SCOPE`, `IDEMPOTENCY_KEY_REUSED`,\n  `RAIL_NOT_AVAILABLE`, `QUOTE_EXPIRED`, …).\n- **`param`** names the offending field when applicable; **`details`** may\n  carry per-field errors.\n- **`request_id`** identifies the exact call - include it in every support\n  request.\n\nThe complete, machine-readable catalog of `code` values ships alongside\nthis reference as `v2-error-codes.json`, so you can generate typed error\nhandling directly from it.\n\n### Resource ids, and what a malformed one returns\n\nThe prefixes below are the ones a path parameter accepts, plus `ozav_apr_`,\nwhich the deprecated `GET /approvals` returns. Other published ids, such as\n`ozav_q_` for quotes, are not used in a path and are not listed here:\n\n| prefix | resource |\n|---|---|\n| `ozav_cus_` | customer |\n| `ozav_ap_` | associated person |\n| `ozav_pd_` | payout destination |\n| `ozav_tr_` | transfer |\n| `ozav_acc_` | account |\n| `ozav_wal_` | wallet |\n| `ozav_card_` | card |\n| `ozav_key_` | API key |\n| `ozav_scr_` | screening |\n| `ozav_vrf_` | verification |\n| `ozav_wh_` | webhook endpoint |\n| `ozav_txi_` | transaction intent |\n| `ozav_apr_` | approval |\n| `ozav_lst_` | list entry (your own block/allow lists) |\n| `ozav_dec_` | decision |\n\n**An id whose prefix does not match the position it was used in returns\n`404`, not `400`.** The request never reaches a lookup - the id could not\nname a resource of that type - but the status is `404` so that one status\ncovers every \"you cannot have this resource\" case. Branch on `code` and\nread `message`, not on the status, to tell the cases apart.\n\nThe message names the prefix that position expects, and, when the id\ncarries a prefix from the table above, names which resource it belongs to\ninstead. Sending a `ozav_cus_…` where a wallet id belongs tells you exactly\nthat.\n\n**What `404` never tells you is whether a resource exists.** A well-formed\nid belonging to another account, to the other environment, or to nothing at\nall all return the same generic phrase, indistinguishable from one another.\nOnly the malformed case gets the extra sentence, because that sentence is a\nstatement about the text you just sent - not about our records.\n\n## Networks & Assets\n\n`components.schemas.Network` is the public v2 wire enum today. OZAV accepts\nonly `USDC` and `USDT` as blockchain assets on this surface today. Other\nstablecoins such as DAI, USDB, PYUSD, and EURC are not accepted by public\nAPI v2 flows unless this table and `GET /capabilities` explicitly say so.\n\n| Network | Public v2 status | Custody stack status | Accepted assets today |\n|---|---|---|---|\n| `ethereum` | accepted | active | USDC, USDT |\n| `base` | accepted | active | USDC |\n| `optimism` | accepted | active | USDC |\n| `polygon` | accepted | active | USDC, USDT |\n| `avalanche_c_chain` | accepted | active | USDC, USDT |\n| `worldchain` | accepted | active | USDC |\n| `unichain` | accepted | active | USDC |\n| `linea` | accepted | active | USDC |\n| `sonic` | accepted | active | USDC |\n| `sei` | accepted | active | USDC |\n| `celo` | accepted | active | USDC |\n| `zksync_era` | accepted | active | USDC |\n| `solana` | accepted | active | USDC, USDT |\n| `tron` | accepted | active | USDT |\n| `stellar` | accepted | active | USDC |\n| `tempo` | not accepted | deferred | none |\n| `arbitrum` | not accepted | active | USDC, USDT |\n| `aptos` | not accepted | deferred | none |\n| `sui` | not accepted | deferred | none |\n\nActive custody stack status does not mean a public end-to-end flow is live.\nWallet provisioning, live card issuance, live BRL/Pix account opening,\nmanaged wallet sends, and self-custody transaction building remain gated unless\n`GET /capabilities` says your organization can use them.\n",
    "contact": {
      "name": "OZAV Platform",
      "url": "https://www.ozav.io",
      "email": "platform@ozav.io"
    },
    "termsOfService": "https://www.ozav.io/terms",
    "license": {
      "name": "Proprietary - © OZAV LTDA",
      "url": "https://www.ozav.io/terms"
    }
  },
  "x-ozav-personas": [
    {
      "key": "wallet-as-a-service",
      "description": "Wallets in your product - self-custody wallets where your users hold the keys, and vaults whose rules and approvers are your own keys. OZAV never decides."
    },
    {
      "key": "compliance-as-a-service",
      "description": "Screenings, decisions and lists for your own customers, as a prepaid service with no KYB of your company."
    },
    {
      "key": "money-movement",
      "description": "Customers, quotes, transfers, accounts, custodial wallets and cards on OZAV rails, after your company's KYB."
    },
    {
      "key": "yield",
      "description": "Yield on stablecoin balances, built for your user's wallet to sign."
    },
    {
      "key": "agents",
      "description": "Spending mandates for AI agents acting for your company (beta)."
    }
  ],
  "servers": [
    {
      "url": "https://developers.ozav.io/v2",
      "description": "Production (live and test keys; environment is derived from the key prefix)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Wallet as a Service Overview",
      "x-displayName": "Overview",
      "description": "**Wallets inside your product, with the keys in your hands or your users'.** There are two models, and\nin both the decisions are yours:\n\n| | Self-custody wallets | Vaults |\n|---|---|---|\n| What it is | An address your user already controls | A wallet whose signing key lives in an enclave OZAV runs |\n| Who signs | Your user, outside OZAV | The vault, after your approvers release the transaction |\n| Who sets the rules | You, in your product | Your administrator keys, sealed inside the vault |\n| Tag | Self-custody Wallets | Vaults |\n\nThe rest of this overview describes **self-custody wallets**. For vaults see the Vaults tag.\n\n**Self-custody wallets.** Register the address your user\nalready controls, read what it holds on the network it was registered on, and get transactions built for\nyour user to sign.\nOZAV never holds the key and never moves the funds.\n\n| | |\n|---|---|\n| Who holds the keys | Your user |\n| Who decides | You, through your API keys. OZAV builds the transaction; your user signs and broadcasts it |\n| Rules | Yours. OZAV runs no KYB and no sanctions screening here: the wallet, the key and the decision are yours |\n| Going live | Accept the terms in the portal and add prepaid credit. No KYB of your company |\n| Networks | Transactions are built for EVM networks |\n\n**The flow**\n\n1. `POST /wallets` registers the address under your own `owner_ref` for that user. No OZAV customer is\n   created.\n2. `GET /wallets/{wallet_id}/onchain-balances` reads what is at the address, per currency, on the network\n   the user registered.\n3. `POST /wallets/{wallet_id}/transaction-intents` builds the unsigned transaction. Your user signs and\n   broadcasts it.\n\n**Access.** The `self_custody` product is enabled per organization, in the sandbox as well: ask OZAV to\nturn it on, and it shows up in `GET /capabilities`.\n"
    },
    {
      "name": "Self-custody Wallets",
      "description": "**Register, read and build.** A wallet here is an address your user already controls: OZAV\nrecords it under your `owner_ref`, reads what it holds on chain, and builds the unsigned transactions your\nuser signs. OZAV never holds the key and never moves funds.\n"
    },
    {
      "name": "Vaults",
      "description": "**Closed for now.** Every route here answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the\nplatform. The model is built and tested, and it has not yet been proven end to end against the production enclave.\nThe `rules` of a vault accept destinations, a cap per send with its asset, and `exportable`; other rule fields\nare refused with `400`, never ignored.\n\nA **vault** is a wallet whose rules and approvers belong to you. OZAV runs the secure enclave that signs,\nbut it holds no key that can sign alone and it decides nothing: your administrator keys set the rules, your\napprover keys release each transaction, and the enclave checks every one of them again before it signs.\n\n**The flow**\n\n1. `POST /vault-roots` registers your administrator keys (Ed25519) and the administrator quorum.\n2. `POST /vault-roots/{root_id}/grants` stores a grant: the approvers (Ed25519, P-256 or passkey), how many\n   must approve, the cap and the expiry. Your administrators sign it, and the API checks the quorum before it\n   stores anything. `POST /vault-roots/{root_id}/grants/{grant_id}/revoke` ends it.\n3. `POST /vaults` creates the vault from a root and its rules, signed by your administrators. It answers\n   `202`: poll `GET /vaults/{vault_id}` until `state` is `active`.\n4. `POST /vaults/{vault_id}/transactions` describes one transfer and returns the **challenge**, a 32-byte\n   hash that names the vault, the grant, the rules, the derivation path and the exact transaction bytes.\n5. Each approver signs the challenge with their own key and calls `POST .../approvals`.\n6. When the quorum is met the vault signs. `GET .../transactions/{transaction_id}` returns the\n   `signed_transaction`, and you broadcast it.\n\n`POST /vaults/{vault_id}/destination-grants` lets your administrators permit new destinations without\nrecreating the vault. `GET /vaults/attestation` returns the enclave attestation, so you can check which code\nis running before you trust it.\n\n**What OZAV does not do.** It holds no key that signs alone: two of the three key shares are outside OZAV, with\nyou. It never approves, overrides a rule or signs without your quorum. It never broadcasts a vault's funds.\n\n**Revoking.** A revoked grant authorizes no new transaction or approval (`409 GRANT_REVOKED`). The check is made\nby the API (`enforcement: \"api\"`): an approval you already signed stays valid inside the enclave until its own\ndeadline.\n\n**Errors you will meet.** `409 VAULT_NOT_ACTIVE` (the vault is still `pending` or `failed`),\n`409 GRANT_REVOKED`, `409 TRANSACTION_NOT_AWAITING_APPROVALS`, `409 TRANSACTION_APPROVAL_WINDOW_CLOSED`,\n`422 GRANT_NOT_FOR_THIS_VAULT` (the grant belongs to another root), `422 APPROVER_NOT_IN_GRANT`,\n`422 APPROVAL_SIGNATURE_INVALID`, `422 GRANT_BELOW_THRESHOLD` and `422 DESTINATION_GRANT_BELOW_THRESHOLD`\n(not enough valid administrator signatures). The malformed-body `400` names the field in `param`.\n\n**Limits.** `user_envelope` up to 16 KiB, `raw_transaction` up to 64 KiB, up to 64 destinations per list,\n32 approvers per grant, 64 signatures per list, and approval windows of up to 7 days. A passkey approver\nneeds `rp_id` and `origins`.\n\n**Access.** The `self_custody` product, with the `wallets:read` and `wallets:write` scopes. No KYB of your\ncompany.\n"
    },
    {
      "name": "Compliance as a Service Overview",
      "x-displayName": "Overview",
      "description": "**Compliance checks inside your own flows.** Screen people and businesses against designated lists,\nask a yes-or-no risk question in one call, and keep your own block and allow lists. OZAV runs the checks\non its own engines and data and returns the evidence. It blocks nothing through these routes: what\nhappens next is your decision, including on a sanctions-list hit. Your decisions apply to your\nscreenings only; the customers OZAV moves money for go through OZAV's own screening.\n\n| | |\n|---|---|\n| Who decides | You, on OZAV's evidence |\n| Going live | Accept the terms in the portal and add prepaid credit. No KYB of your company |\n| Your data | No customer record is needed: send the subject inline. A decision stores the verdict, never your request |\n\n**The flow**\n\n1. `POST /decisions` answers one question in one call: `ALLOW`, `REVIEW` or `DENY`, with the reasons and\n   the checks behind it.\n2. `POST /screenings` returns the matches themselves when you need them: which list, which name, how\n   strong the match is.\n3. `PATCH /screenings/{screening_id}/hits/{hit_id}` records your decision on a hit, sanctions lists\n   included.\n4. `POST /lists/entries` keeps your own block and allow lists, applied to every screening.\n5. `POST /decisions/{decision_id}/outcome` records what you did with a decision.\n"
    },
    {
      "name": "Decisions",
      "description": "**One call, one verdict.** Ask a yes-or-no risk question and get `ALLOW`, `REVIEW` or `DENY`, with\nthe reasons and the checks behind it.\n\n| `type` | The question |\n|---|---|\n| `name.match` | Is this name on a sanctions list? |\n| `destination.ownership` | Is this Pix destination the customer's own account? |\n| `wallet.risk` | Is this address on a sanctions list? |\n| `transaction.outbound` | All of the above for one outgoing payment; the most severe verdict wins |\n\nEvery verdict comes from OZAV's own engines and data, with no third-party call. Missing or stale data\nanswers `REVIEW`, never `ALLOW`, and `DENY` comes only when OZAV holds the proof: an address on the list,\nor a listed name matched by the date of birth. The verdict is advice; what you do with it is your\ndecision. `wallet.risk` covers sanctions lists only, not blockchain analytics.\n\nReport what you did with `POST /decisions/{decision_id}/outcome`. OZAV stores the verdict, never your\nrequest.\n"
    },
    {
      "name": "Screenings",
      "description": "**The matches themselves, when you need to see them.** Screen a person, a business or an existing\ncustomer against OZAV's designated-persons data (UN, OFAC, EU, UK and national sources, refreshed daily),\nwith no customer record required. Each hit names the list, the matched name and how strong the match is.\nAn inconclusive subject is flagged for review, never cleared silently.\n\nRecord your decision on a hit with `PATCH /screenings/{screening_id}/hits/{hit_id}`. Your decision is\nthe final one, on any hit.\n"
    },
    {
      "name": "Lists",
      "description": "**Your own block and allow lists**, applied to every screening on top of OZAV's data: names,\ndocuments, e-mails and e-mail domains.\n"
    },
    {
      "name": "Money Movement Overview",
      "x-displayName": "Overview",
      "description": "**A regulated platform that moves money for your customers.** Onboard a customer once, then open\nnamed accounts, price and execute FX, pay out, hold balances in custody and issue cards. OZAV operates\nevery step and runs the compliance behind it.\n\n| | |\n|---|---|\n| Who decides | OZAV, as the regulated operator: customer approval and limits |\n| Going live | KYB of your company and a signed contract. OZAV issues your live keys |\n| Billing | Monthly invoice |\n| Pricing | Exchange conditions: a spread in integer basis points, on every quote |\n\n**The flow**\n\n1. **Check your access.** `GET /capabilities` returns the products enabled for your organization and\n   what your key can call.\n2. **Onboard the customer.** `POST /customers` with the profile, then the documents with\n   `POST /customers/{customer_id}/documents`. In `reliance` mode, the default for API integrators,\n   OZAV reviews the completed profile and the identity document (the company registration for a\n   business). In `full` mode, `POST /verifications` runs the identity check, and on `live` the customer\n   first accepts the terms: `POST /customers/terms-sessions` and `POST /customers/terms-sessions/accept`,\n   then pass the accepted `terms_session_id` on `POST /customers`. OZAV decides; follow it on\n   `GET /customers/{customer_id}` or the `customer.updated` webhook.\n3. **Receive.** `POST /accounts` opens a named account for the customer: in the customer's own name on\n   Brazilian rails, and segregated and attributed to the customer under OZAV on foreign-currency rails.\n4. **Price.** `POST /quotes` returns the exchange conditions before anything moves.\n5. **Move.** `POST /transfers` for on-ramp, off-ramp and FX; `POST /payout-destinations` registers\n   where the money lands. A Pix destination must be in the customer's own name, unless OZAV has\n   enabled third-party payouts for your organization; the other rails can pay a third party.\n6. **Hold.** Custodial wallets: `GET /wallets`, balances, deposit addresses, send.\n7. **Spend.** `POST /cards` issues a card on the customer's balance.\n8. **Reconcile.** `GET /transactions` lists every movement in one place.\n\n### Accounts and wallets are different things\n\nThey are asked for separately, they hold different value, and they demand\ndifferent evidence. Integrators conflate them constantly, so here is the\ncontrast in one place.\n\nAn **account** (`ozav_acc_`) is a named fiat receiving instruction. \"Named\"\nis the load-bearing word: the instruction carries your end customer's\nidentity rather than being a shared OZAV address. That is why it needs\nidentity - a bank credits a person, and the person has to be established\nbefore the instruction exists.\n\nHow literal \"named\" is depends on the rail. On Brazilian rails the account\nis held in your end customer's own name at the institution. On the\nforeign-currency rails the holder of record is OZAV, and the account is\nsegregated and attributed to your end customer - the same money separation,\na different legal holder. If who holds the account at the institution\nmatters to your contracts, treat those two as different products.\n\nAccounts are created with `POST /accounts`, which requires an `approved`\ncustomer and can still refuse: activation for the money paths is a further\ninternal step with no public field (see \"`approved` is not the finish\nline\" in the **Customers** tag).\n\nA **wallet** (`ozav_wal_`) holds crypto. It comes in two kinds, and only one\nof them is ever created through the API:\n\n- **managed** - OZAV holds the key. It is provisioned as part of the\n  customer when your organization has the custody product; you never request\n  it directly.\n- **self-custody** - your end user holds the key and the address already\n  exists. `POST /wallets` **records** it. Nothing is created, and OZAV never\n  holds those funds.\n\n| | account | wallet |\n|---|---|---|\n| what it holds | fiat, at a bank | crypto, on a chain |\n| created by | `POST /accounts` | managed: with the customer. self-custody: `POST /wallets` |\n| needs identity? | yes - customer `approved`, and activation can still refuse | no - self-custody records an address |\n| needs an address? | no | self-custody: yes, and it must match the chain |\n\nRead `POST /wallets` for what recording an address does and does not prove.\n\nRecording a self-custody address needs no identity, but it does need the\n`self_custody` entitlement on your organization - a newly created\norganization does not get it by default and receives `403\nENTITLEMENT_REQUIRED`. A customer that already holds a managed wallet is\nrefused with `409 WALLET_ALREADY_MANAGED`: a customer is one or the other,\nnever both.\n\n### In the sandbox\n\nRead this before you plan a schedule around it. Each row below is a place\nwhere the resource is created but the flow cannot be finished with a\nself-serve `test` key, and each needs OZAV to act on your account. None of\nthem is a bug you can work around by changing your request.\n\n| You want to | What happens | What unblocks it |\n|---|---|---|\n| Create a `full` customer and then verify it | The sandbox create already answers `status: approved` for a `full` customer (the simulated settlement path reports it active), so a flow tested only here never reaches `POST /verifications`. A `reliance` customer is `pending` in both environments until profile + screening are in and, for an individual, its `identity_document` is `received` (in sandbox its presence is enough) | Nothing to unblock - a warning: on `live` a `full` create answers `pending` and only a verification advances it. Exercise `POST /verifications` + `/simulate` in sandbox anyway, so the step exists in your code. |\n| Create a customer that settles | Works, but the sandbox does not exercise the terms gate: the attestation is simulated, and a missing, unknown or foreign `terms_session_id` is accepted with `201` | Nothing to unblock, and one thing to know: on `live` the same call for a `full` customer is refused with `422 TERMS_ACCEPTANCE_REQUIRED` unless you pass an accepted session (`reliance` customers skip this). Collect it with `POST /customers/terms-sessions` and `POST /customers/terms-sessions/accept` (both public), and pass the id on `POST /customers`. Build that into your flow now; the sandbox will not remind you. |\n| Render the Pix payment screen | The sandbox does not confirm the pay-in before responding: it holds the transfer in `awaiting_payment` and returns `source.payment_instructions` carrying a simulated BR Code, a real EMV MPM payload with a valid CRC that a genuine Pix library parses | Nothing to unblock. To move past the payment, call `POST /transfers/{transfer_id}/simulate`: it confirms the pay-in the way a real payer would and advances the transfer through both legs to a terminal `completed`, emitting `transfer.updated` and then `transfer.completed`. |\n| Pay out to a Pix key (off-ramp) | Works end to end: the sandbox pays the destination, the transfer lands `completed` and `transfer.completed` fires. | Nothing to unblock. In `live` a Pix-key destination is payable as soon as it is `active` too; read `capabilities.receive` on the destination (true for an active Pix-key destination in both environments), never `status`. |\n| Test a card authorization | Every `/cards` route answers `403 ENTITLEMENT_REQUIRED` when your organization does not have the `cards` product. A sandbox organization receives it at creation; an older one may not have it | Check `entitlements` on `GET /capabilities` and ask OZAV to grant `cards` if it is missing. After that, a customer's first card is `active` but the customer's card balance starts at zero in the sandbox, so every purchase declines with `Insufficient balance` until you load it with `POST /cards/{card_id}/simulate/funding`. The 10,000 USDC the sandbox wallet shows does not fund cards. See `funding_type` on `Card`. |\n| Send a user to hosted verification | A `test` key never starts a hosted session (a real one bills a third party per attempt), so `session_url` is absent | Drive the outcome with `POST /verifications/{id}/simulate`, which is the sandbox affordance. On `live`, also ask for a hosted capability and ask OZAV to enable hosted capture; see `session_url`. |\n| Open a named account for a holder | The sandbox runs the same profile gate as `live`, only shallower. It still requires the customer to be approved, still applies the rail matrix, and still requires a document that passes its own check digit (11 digits for CPF, 14 for CNPJ) plus a holder name, refusing with the same `422 ACCOUNT_PROFILE_INCOMPLETE` you would get on `live`. For a Brazilian business it also requires the two declarations, `business.bc_protege_disabled` and, when the institution asks for the terms, `business.account_opening_terms_accepted_by`. What it skips is everything else the institution asks for (address, phone, mother's name, the KYB shareholder packet, uploaded files), so it opens with **no supporting documents at all** | Nothing to unblock: this one is a warning, not a limitation. A sandbox account opening proves your request is well-formed and the holder's core identifiers are present, never that the holder is ready. Send the documents with `POST /customers/{customer_id}/documents` first; the `422 ACCOUNT_PROFILE_INCOMPLETE` names every field and document still missing, and it is the authoritative checklist. **But one refusal is not on that checklist, and no document clears it:** a BRL named account for `type: individual` refuses on `live` with `422 ACCOUNT_HOLDER_TYPE_NOT_SUPPORTED`, because no named account opens for an individual on that rail today - and the sandbox opens it anyway. If your flow is individual + BRL, sending more documents will not change the answer; see \"Opening an account in USD, EUR or GBP\" in the **Customers** tag. |\n| Read a wallet balance | `GET /wallets/{wallet_id}/balances` in `test` starts every wallet at a round 10,000.00 USDC and USDT and then moves with the transfers that completed on it - an on-ramp credits the amount delivered, an off-ramp debits the amount sent. | Nothing to unblock, and three things to know. It is a derived figure, not a ledger: it does not reflect crypto sent to the wallet from outside, nor card spend. `GET /wallets/{wallet_id}/deposit-addresses` and `/onchain-balances` stay empty in `test`, because a sandbox wallet has no chain addresses. And the opening 10,000 is a round number, so nobody mistakes it for money. |\n\nEverything else in this section works end to end in the sandbox, including\na full BRL on-ramp transfer, screening against designated lists with no\ncustomer record, document upload, and webhook delivery with signature\nverification.\n"
    },
    {
      "name": "Capabilities",
      "description": "**Know what your key can do before you call.** `GET /capabilities` returns the products enabled for\nyour organization, the scopes on the key you call it with, and what each feature needs. Branch on it\ninstead of guessing.\n"
    },
    {
      "name": "Customers",
      "description": "**Onboard once, then move money.** A customer is the person or business OZAV moves money for: an\n`individual` or a `business`, created once with the identity you hold, with associated persons (owners,\ndirectors, signers) for a business. Accounts, payout destinations, custodial wallets, quotes and transfers\nall reference its `customer_id`.\n\nOZAV, as the regulated operator, verifies the customer and decides; follow `decision` on\n`GET /customers/{customer_id}` or the `customer.updated` webhook. What is still owed comes back on the\nrecord itself, in `requirements`. Creating the customer asks for no documents: register them with\n`POST /customers/{customer_id}/documents` when `requirements` asks.\n\n### What each customer type must provide\n\nThe required set is driven by the **country on the tax identity**, not by\nnationality. A Brazilian tax id (`cpf`, or `country: BR`) puts the customer\non the Brazilian profile; anything else puts them on the base profile. Send\n`nationality` if you have it - it feeds risk assessment and will not change\na single required field.\n\n`POST /customers` always requires `type`, `email`, `legal_name` and the tax\nidentity for the type - and a tax identity is three parts, not one:\n`value`, `type` and `country`. Every address also carries `country`.\n\nThe tables below are the **request fields**: send all of them and the\ncustomer is created and complete. They are not a transcription of\n`requirements.missing_fields`, which reports one flat token per concept from\na closed list (`phone`, `residential_address`, `tax_id`, ...) and never a\nsub-path like `phone.country_code`. Read the tables to build the request;\nread `missing_fields` to find out what a customer you already created is\nstill missing.\n\n##### Individual, tax id issued in Brazil\n\n| field | rule |\n|---|---|\n| `individual.legal_name` | full legal name |\n| `individual.tax_id.value` | the CPF; its check digit is verified |\n| `individual.date_of_birth` | ISO date |\n| `individual.phone.country_code` | dial code |\n| `individual.phone.number` | subscriber number |\n| `email` | top level, not inside the block |\n| `individual.residential_address.line1` | street |\n| `individual.residential_address.number` | required in Brazil |\n| `individual.residential_address.neighborhood` | required in Brazil |\n| `individual.residential_address.city` | non-blank |\n| `individual.residential_address.state` | required in Brazil |\n| `individual.residential_address.postal_code` | required in Brazil |\n| `individual.residential_address.country` | `BR`; required on every address |\n| `individual.source_of_funds` | declaration |\n| `individual.expected_monthly_volume_usd` | declaration |\n| `individual.monthly_income_usd` | declaration; the capacity the expected volume is measured against |\n| `individual.occupation` | profession |\n| `individual.nationality` | ISO 3166-1 alpha-2 |\n| `individual.is_pep` | optional politically-exposed-person declaration; see below the tables |\n| `individual.account_purpose` | why the account is being opened |\n\n##### Individual, tax id issued outside Brazil\n\n| field | rule |\n|---|---|\n| `individual.legal_name` | full legal name |\n| `individual.tax_id.value` | `ssn`, `nif`, `passport` or `other`; no check digit is applied |\n| `email` | top level |\n| `individual.date_of_birth` | ISO date; every individual owes it, not only Brazilian ones |\n| `individual.phone.country_code` | dialling code, e.g. `+1`; a foreign-currency account cannot be opened without it |\n| `individual.phone.number` | national number |\n| `individual.residential_address.line1` | street |\n| `individual.residential_address.city` | non-blank |\n| `individual.residential_address.postal_code` | required for any account, not only Brazilian ones: the named-account holder registration refuses without it |\n| `individual.residential_address.country` | ISO 3166-1 alpha-2 |\n| `individual.phone.country_code` | dialling code, digits only, no `+` |\n| `individual.phone.number` | mobile; a landline is not accepted for the named-account holder record |\n| `individual.source_of_funds` | declaration |\n| `individual.expected_monthly_volume_usd` | declaration |\n| `individual.monthly_income_usd` | declaration; the capacity the expected volume is measured against |\n| `individual.occupation` | profession |\n| `individual.nationality` | ISO 3166-1 alpha-2 |\n| `individual.is_pep` | optional politically-exposed-person declaration; see below the tables |\n| `individual.account_purpose` | why the account is being opened |\n\n**`individual.is_pep` is optional.** OZAV screens every individual customer\nagainst politically-exposed-person lists. An individual you did not declare,\nwhose recent OZAV screening read the PEP lists without a match, is recorded\nby OZAV as not a PEP, and `requirements.missing_fields` does not ask for\n`is_pep`. A declaration you send always takes precedence; `true`, or a PEP\nmatch in OZAV's screening, goes to OZAV compliance review.\n\n##### Business, registration issued in Brazil\n\n| field | rule |\n|---|---|\n| `business.legal_name` | full legal name |\n| `business.registration.value` | the CNPJ; its check digit is verified |\n| `business.phone.country_code` | dial code |\n| `business.phone.number` | subscriber number |\n| `email` | top level |\n| `business.registered_address.line1` | street |\n| `business.registered_address.number` | required in Brazil |\n| `business.registered_address.neighborhood` | required in Brazil |\n| `business.registered_address.city` | non-blank |\n| `business.registered_address.state` | required in Brazil |\n| `business.registered_address.postal_code` | required in Brazil |\n| `business.registered_address.country` | `BR`; required on every address |\n| `business.company_type` | legal form |\n| `owners` | at least one associated person owning 25% or more |\n| `representative` | at least one associated person who can sign for the company (`director`, `officer` or `authorized_signer`). Separate from `owners`: a sole owner with only the `ubo` role does not satisfy it |\n| `business.source_of_funds` | declaration |\n| `business.expected_monthly_volume_usd` | declaration |\n| `business.annual_revenue_usd` | declaration; the capacity the expected volume is measured against |\n| `business.expected_daily_volume_usd` | declaration; not derived from the monthly figure |\n| `business.is_regulated_activity` | boolean; `false` counts as answered |\n| `business.nature_of_business` | what the company actually does |\n| `business.website` | public site or profile |\n| `business.account_purpose` | why the account is being opened |\n| `business.incorporation_date` | ISO date |\n\n##### Business, registration issued outside Brazil\n\n| field | rule |\n|---|---|\n| `business.legal_name` | full legal name |\n| `business.registration.value` | `ein`, `company_number` or `other`; no check digit is applied |\n| `email` | top level |\n| `business.phone.country_code` | dialling code, e.g. `+1`; a foreign-currency account cannot be opened without it |\n| `business.phone.number` | national number |\n| `owners` | at least one associated person owning **25% or more**. The 25% floor is FATF, not a Brazilian rule |\n| `representative` | at least one associated person who can sign (`director`, `officer` or `authorized_signer`); without one the account holder stays pending at the bank and never receives coordinates |\n| `business.registered_address.line1` | street |\n| `business.registered_address.city` | non-blank |\n| `business.registered_address.postal_code` | required for any account, not only Brazilian ones: the named-account holder registration refuses without it |\n| `business.registered_address.country` | ISO 3166-1 alpha-2 |\n| `business.phone.country_code` | dialling code, digits only, no `+` |\n| `business.phone.number` | mobile; a landline is not accepted for the named-account holder record |\n| `business.source_of_funds` | declaration |\n| `business.expected_monthly_volume_usd` | declaration |\n| `business.annual_revenue_usd` | declaration; the capacity the expected volume is measured against |\n| `business.expected_daily_volume_usd` | declaration; not derived from the monthly figure |\n| `business.is_regulated_activity` | boolean; `false` counts as answered |\n| `business.nature_of_business` | what the company actually does |\n| `business.website` | public site or profile |\n| `business.account_purpose` | why the account is being opened |\n| `business.incorporation_date` | ISO date |\n\n##### Conditional: a business that declares a regulated activity\n\nThe four tables above list what is asked of every customer of that shape.\nOne requirement is conditional and therefore is not a row in them.\n\nWhen `business.is_regulated_activity` is `true`, two further fields become\nrequired and appear in `requirements.missing_fields`:\n\n| field | rule |\n|---|---|\n| `business.regulator` | name of the supervising authority |\n| `business.regulated_activity_details` | licence or scope of the regulated activity |\n\nThey are asked for **only** on a `true` answer. `false` is an answer - the\ncompany has declared it is not regulated, and asking who supervises it would\nbe asking for the detail of something it denied. An **absent**\n`is_regulated_activity` is an open question: `missing_fields` asks for the\nquestion itself and not for its follow-up, so you are never asked to name a\nregulator for an activity you never said was regulated.\n\n##### Opening an account in USD, EUR or GBP\n\n**Read this before you build against it.** `sepa`, `swift` and\n`faster_payments` are **live behind enablement** - the corridor is served in\nproduction, but opening an account for a given customer also requires that\nOZAV has enabled that customer for foreign-currency named accounts. A `live`\nrequest for a customer who is not enabled is refused with a named error that\nsays so.\n\n`fedwire` stays `sandbox only`: no US-domiciled account has ever been issued\non this corridor. A USD account is issued as a **GB IBAN** and reached by\n`swift`; build against that shape.\n\n`Rail.x-ozav-rail-availability` is the authoritative table; this paragraph\ndescribes it and is checked against it.\n\nThe field list below is published so you can build the request now and not\nchange it later - it is what the holder record asks for. It is not a\nstatement that the account opens on a `live` key today. Availability is\nanswered by that table, never by this section.\n\n**Business customers only, today.** A foreign-currency account is filed\nagainst a holder record registered as a company, so\n`type: individual` is refused there with\n`422 ACCOUNT_HOLDER_TYPE_NOT_SUPPORTED`. Opening one would file a natural\nperson as a company, with their personal tax id standing in for both the\ncompany's tax id and its registration number.\n\n**BRL/Pix is the same today, and the sandbox does not enforce it.** A\n`live` key asking for a BRL named account with `type: individual` is\nrefused with `422 ACCOUNT_HOLDER_TYPE_NOT_SUPPORTED`. A `test` key opens it\nanyway - the sandbox provisioner does not read the holder type, so it\nanswers `201` for exactly the request `live` refuses. An individual BRL\nflow built against a test key will not survive the switch to a live key,\nand the sandbox will not warn you.\n\nAn individual can still be onboarded, verified, and hold a wallet. Build\nnamed accounts for `type: business`.\n\n**A company registered outside Brazil can hold one.** `registration.type`\nmay be `ein`, `company_number` or `other`, and the value is sent as declared\n- no Brazilian format is imposed on a foreign registration, and\n`registration.country` is what determines the country of record.\n\nThe reverse also holds: an account on a\n**Brazilian** rail does need a Brazilian tax identity. A customer whose\nregistration is an `ein` is refused there with\n`422 ACCOUNT_HOLDER_TYPE_NOT_SUPPORTED`, naming the declared type - the\ncustomer is valid, only that corridor needs a CPF or CNPJ.\n\nRegistering that holder needs a slightly fuller set than onboarding alone,\nand the difference is the part integrators hit late:\n\n| field | note |\n|---|---|\n| `email` | top level |\n| `business.phone.number` | the subscriber number |\n| `business.phone.country_code` | dial code, e.g. `55` |\n| address `line1` | street |\n| address `city` | non-blank |\n| address `postal_code` | **required here**, and onboarding does not ask for it |\n| address `country` | **required here**, ISO 3166-1 alpha-2 |\n| address `state` | optional |\n\n**And one authorised person, by name.** The holder record is not complete\nwithout a natural person attached to it - the individual who controls or\nsigns for the company. A holder with nobody attached stays pending at the\nbank and never receives coordinates.\n\n| field | note |\n|---|---|\n| person `firstName` | given name, as on the identity document |\n| person `lastName` | family name |\n| person `birthDate` | ISO date, `YYYY-MM-DD` |\n| person `roles` | at least one of `BeneficialOwner`, `AuthorizedPerson`, `Director` |\n| person `gender` | `male`, `female` or `other`, as the person declares it - send `individual.gender` on the associated person |\n| person `ownershipPercent` | only when the person is an owner; omitted otherwise |\n\n**`legal_name` on an associated person must be at least two words.** OZAV\nsplits it into a given and a family name for the bank record, and a\nsingle-word name cannot be split. A person whose name will not split is\nskipped when OZAV picks who signs, so if they were your only signer the\nholder is left with nobody attached. `requirements` reports it as\n`individual.legal_name`, the same token as a missing name, because the fix\nis the same: correct the name.\n\n`ownershipPercent` is sent **only** for a person declared as an owner.\nDeclaring a stake for someone who does not hold one is a false ownership\nstatement, and OZAV will not make it on your behalf.\n\n**`postal_code` and a structured `phone` are the two that catch people\nout.** Outside Brazil, `requirements.missing_fields` asks only for\n`line1`, `city` and `country` on the address, and it does not ask for a\npostal code at all - that set is enough to complete onboarding, and not\nenough to open this account. Send both up front and you will not have to go\nback to your customer for them later.\n\n**Ownership.** This account can only be opened for a holder with a single\ncontrolling owner, and OZAV has to be able to establish that it is one. A\ncompany whose ownership is split across several qualifying owners - or whose\nownership OZAV cannot resolve - is refused rather than filed under a\ndeclaration that does not describe it. If that is your case, talk to us\nbefore integrating.\n\n**These refusals name the customer, not the corridor.** They arrive as\n`422 ACCOUNT_HOLDER_TYPE_NOT_SUPPORTED`, a different code from\n`ACCOUNT_RAIL_NOT_AVAILABLE`: requesting a different currency or\nrail will not change the answer, because the answer is about who the holder\nis.\n\n**Approval is not instant, and the wait has its own code.** Once the\nholder is registered, the institution that holds the account reviews it\nbefore the account can receive anything, and states 24 to 48 hours for that. A `POST /accounts`\nduring that window returns `409 ACCOUNT_HOLDER_PENDING_APPROVAL`. Nothing in\nyour request is wrong and there is no field to correct - send the same\nrequest again once the review clears.\n\nDo not poll it in a tight loop. The code is the same until the review\nfinishes, so retrying every few seconds only produces the same answer.\n\n**Documents are required here too.** The `POST /accounts` call itself\ncarries structured data only, with no file attached - but that is a fact\nabout the call, not about the requirement. **A holder with no dossier is a\nbank account that never receives.**\n\nThe dossier is the same list as for a Brazilian account - the six below,\nplus an identity document for each associated person. Send it once; it is\nnot collected twice because a second currency was opened later.\n\n**Sending the dossier does not, by itself, make the account receive.** The\ndocuments are what a compliance review needs in order to run; the review\nitself happens at the institution, on its own clock, and the account becomes\npayable when that review clears - not when the upload succeeds. A holder\nstill awaiting that review can sit unchanged for weeks.\n\nSo: a complete dossier is the part you and OZAV control, and it is a\nprecondition, not a switch. Plan the go-live of a foreign-currency corridor\naround a review you do not schedule, and do not quote your own customer a\ndate that depends on it.\n\n##### Which documents, for whom, and for which account\n\nTwo mechanisms establish identity. An individual uses only the first; a\nbusiness uses both.\n\n| | how identity is established | files you upload |\n|---|---|---|\n| individual | a hosted verification session captures the document, liveness and face match (`POST /verifications`) | none |\n| business | hosted session for KYB/UBO, **plus** the company dossier you upload (`POST /customers/{customer_id}/documents`) | the list below |\n\nAn individual is not asked for file uploads. Start a verification instead\nand the session collects the identity artifacts directly from the person -\nthat is what `kyc_document`, `liveness` and `face_match` mean in\n`CapabilityKind`. Uploading files for an individual is neither required nor\na substitute for the session.\n\n**Proof of address and a bank statement are asked of every business**,\nwhatever currency it holds: they are KYB, not a property of a payment rail.\nA Brazilian company is additionally asked for the rail's own list, so the\nBrazilian set is the larger one.\n\n**A tax return is not required of anyone** - not a business, not an\nindividual, at any volume. What OZAV needs is economic capacity, and that is\nalready a required field (`annual_revenue_usd` for a business,\n`monthly_income_usd` for an individual). A tax return varies by jurisdiction\nand much of the world has no direct equivalent; the structured field answers\nthe same question and compares across borders.\n\nBoth sets are collected the same way, and OZAV keeps one canonical record:\nnothing you already sent has to be sent again because a rail was chosen\nlater. A Brazilian account chosen later is still the first time the rail's\nextra documents are asked for - plan for that if you may offer BRL later,\nbecause collecting from your customer after the fact is exactly the\nround trip this section exists to prevent.\n\n##### Supporting documents a business must upload\n\n**Uploading a document is two calls, and the first one alone does not\ncount.** `POST /customers/{customer_id}/documents` registers the document and\nreturns an `upload_url`; the record sits at `pending_upload`, which is\ntreated as **not sent**. The bytes go in the second call, and only then does\nthe record become `received` - the state the account gate accepts.\n\nA `201` from the first call is not a delivered document. If you stop there,\nthe dossier looks complete on your side and is empty on ours, and the account\nis refused for a document you believe you sent. (There are records sitting at\n`pending_upload` in production today for exactly this reason.)\n\n`requirements.missing_fields` covers request fields. Documents are uploaded\nthrough `POST /customers/{customer_id}/documents` **plus the upload call**,\nand are not reported there, so they are listed here instead - opening a Brazilian account asks\nfor all seven, plus four documents per associated person (see below), before it will\nproceed. A `test` key does not enforce this: the sandbox opens the account\nwith no documents at all, so sandbox success is not evidence the dossier is\ncomplete.\n\n**Documents are required in every currency.** Opening a `live` account in\nUSD, EUR or GBP applies the same gate as BRL, with the same\n`422 ACCOUNT_PROFILE_INCOMPLETE` and the same `missing` list, entries\nprefixed `documents.`:\n\n| holder | documents required to open a non-BRL account |\n|---|---|\n| individual | `identity_document`, `proof_of_address`, `bank_statement` |\n| business | `company_registration`, `ownership_structure`, `proof_of_address`, `bank_statement` |\n\nA Brazilian business asks for more, because a Brazilian payment institution\ndoes: `company_registration`, `tax_registration` (the Cartão\nCNPJ), `ownership_structure`, `proof_of_address`, `financial_statement`\n(satisfied by `income_tax_return` too - a balance sheet, a DRE or the IRPJ;\na bank statement is not accepted as economic capacity), `bank_statement` and\n`source_of_funds`; and, for each declared associated person (every person\nyou register, whatever the share - the payment institution reviews all of\nthem as representatives): `identity_document`, `selfie` (or `liveness`) and\n`proof_of_address` (upload with `subject_type: associated_person`). The\nperson's politically-exposed-person declaration (`pep_declaration`) is\noptional; see below for the one account that asks for it. You do not have to\nreconcile the two lists yourself: `GET /customers/{customer_id}/documents`\nreturns `still_required` for that customer, and it is computed by the same\ncode the account gate runs - what it names is exactly what the gate will ask\nfor.\n\nThe per-person list comes back the same way: `GET /customers/{customer_id}/documents`\nreturns `still_required_by_person`, one entry per associated person with that\nperson's public id, `still_required` and `pending`; and `POST /accounts` refuses\nwith the same names, `associated_persons[i].documents.<doc_type>`, in the same\n`missing` list as the company's `documents.<doc_type>`.\n\n| `doc_type` to send | what it establishes |\n|---|---|\n| `company_registration` | the company exists and is currently registered |\n| `ownership_structure` | who ultimately owns it |\n| `proof_of_address` | where it operates from |\n| `financial_statement` | its financial position |\n| `bank_statement` | recent account activity |\n| `source_of_funds` | where the money comes from |\n| `tax_registration` | The company's tax registration card (Brazil: Cartão CNPJ). Required for a Brazilian business. |\n| `income_tax_return` | The company's income tax return (Brazil: IRPJ). Satisfies `financial_statement` as economic capacity; a bank statement does not. |\n| `selfie` | An associated person's face image (`subject_type: associated_person`); `liveness` satisfies it too. Required per ≥25% owner or signer of a Brazilian business. |\n| `pep_declaration` | An associated person's own politically-exposed-person declaration (`subject_type: associated_person`). Optional; only a BRL account asks for it (see below). Instead of uploading it, you can answer `individual.is_pep` on each associated person. |\n\n**BC Protege+ must be off.** A Brazilian business also declares\n`business.bc_protege_disabled: true` (`PATCH /customers/{customer_id}`) once it\nhas turned off BC Protege+ with its own gov.br login\n(https://www.gov.br/pt-br/servicos/proteger-se-com-o-bc-protege) - with the service on, no\ninstitution can open an account in its name. Until then `POST /accounts` lists\n`business.bc_protege_disabled` in `missing` (sandbox too, where the same\n`PATCH` clears it).\n\n**The account opening terms.** When the institution that opens the BRL\naccount asks for it, a legal representative accepts the account opening\nterms on behalf of the business before the account is requested. You do\nnot need to know whether it asks: you can declare the acceptance for any\nBrazilian business before `POST /accounts`, and when the institution does\nnot ask for the terms it is recorded and has no effect. Send\n`business.account_opening_terms_accepted_by` - the id of that associated\nperson of the business (pending or approved, not suspended), who needs a\nBrazilian CPF in `individual.tax_id` - with\n`PATCH /customers/{customer_id}`. OZAV records the acceptance as declared\nby your organization through the API and delivers the institution's term\non its next account-opening pass (every 30 minutes); sending the same person again, with the same name and CPF, records no new acceptance. Until then\n`POST /accounts` refuses with `422 ACCOUNT_PROFILE_INCOMPLETE`, with\n`business.account_opening_terms_accepted_by` as an `error.details[].param`\n(code `missing_field`; sandbox too), and the open item in\n`requirements.documents_due` has the key `account_opening_terms_acceptance`.\n\n**The PEP declaration is optional.** OZAV screens every associated person\nagainst politically-exposed-person lists. A person you did not declare,\nwhose recent OZAV screening read the PEP lists without a match, is recorded\nby OZAV as not a PEP, and nothing is asked of you. A declaration you send\nalways takes precedence, and `true` (or a PEP match in OZAV's screening)\ngoes to OZAV compliance. `GET /customers/{customer_id}/documents` and the\naccount gate do not list `pep_declaration`.\n\nThe exception is a BRL account: the Brazilian payment institution requires\na declaration collected from the customer, and OZAV's screening does not\nreplace it. For a BRL account, once the per-person documents above are in,\n`POST /accounts` lists `associated_persons[i].documents.pep_declaration` in\n`missing` until each person has one (it is checked with the institution's\nown requirements, after the general list), and the item appears in\n`requirements.documents_due` once the institution asks for it. Answering\n`individual.is_pep` early avoids that second round.\n\n**For a BRL account the PEP declaration can be an answer instead of a file.** Send\n`individual.is_pep` (`true`/`false`) when you create or update each associated\nperson (`POST`/`PATCH /customers/{customer_id}/associated-persons`). When every\nassociated person of the business answers `false`, OZAV issues the\n`pep_declaration` itself - one term per business, citing the field, your\norganization and when each answer was sent - on its next account-opening pass\n(every 30 minutes), and the requirement is met. OZAV only issues the term when\nits own recent screening of every associated person read the PEP lists without\na match; until then the term waits (no action needed from you). `true` on\nanyone, a person without an answer, or a PEP match in OZAV's own screening of\nany person means OZAV does not issue the term: `pep_declaration` stays in the\n`missing` list, and you send it as a document for that person. The term speaks\nfor the whole business, so it is withdrawn for the whole business: if, while\nthe business account is being opened (on OZAV's next pass, every 30 minutes),\nany person has `is_pep: true` or OZAV's screening finds a PEP match on anyone,\nevery `pep_declaration` document of the term (one per person) becomes\n`rejected`, `pep_declaration` goes back to `missing` and its item in\n`requirements.documents_due` back to `due` with a new 30-day `due_at` and\n`reason_code: document_invalid`, announced by a `requirement.due` webhook.\nFrom the moment a person has `is_pep: true`, the term already does not count\nfor opening the account (`POST /accounts` lists `pep_declaration` in\n`missing`), even before that pass. Send a `pep_declaration` document for each\nperson; OZAV issues a new term only when every answer is `false` again and its\nscreening of everyone is clear. A `PATCH` of an associated person whose\n`individual` leaves out `is_pep` keeps the answer already given; send `is_pep`\nto change it.\n\nFor USD, EUR and GBP accounts, the authorized person of a business must also\ndeclare `individual.gender` (`male`, `female` or `other`). Until it is sent, the\naccount stays pending. As with `is_pep`, a `PATCH` that leaves it out keeps it.\n\nThese are the literal `doc_type` values - any other string is rejected with\n`400`. For a non-Brazilian business one `bank_statement` counts for two of\nthem (financial position and recent activity). For a Brazilian business it\ndoes not: a bank statement covers recent activity only, and the financial\nposition needs `financial_statement` or `income_tax_return` - seven\nuploads for the seven requirements, plus the per-person ones.\n\nThree tokens in the `missing` list of `POST /accounts` are not `doc_type`\nvalues and must not be echoed as one: `documents.financial_statement_or_income_tax_return`\n(send either `financial_statement` or `income_tax_return`),\n`associated_persons[i].identity_document_or_approval` (send `identity_document`\nfor that person, or have the person approved) and\n`associated_persons[i].representative_picture_or_waiver` (send `selfie`).\nThe `i` in `associated_persons[i]` is the person's position ordered by\ncreation time, oldest first - the opposite of `GET /customers/{customer_id}/associated-persons`,\nwhich lists newest first. `still_required_by_person` carries the person's\npublic id precisely so you never have to resolve that index.\n\nA business also needs at least one associated person whose `roles` include\n`authorized_signer`, `officer` or `director`, in addition to the ≥25% owner\nthat `missing_fields` reports as `owners` (role `ubo`). They can be the same\nperson, and `roles` takes more than one value.\n\n**Each of those people needs an identity document of their own.** Send it\nas `identity_document` - it is not covered by any of the six company\ndocuments above, and it is the one integrators discover last, after the\ndossier is already assembled. A person OZAV has already approved does not\nneed it re-sent.\n\n**You do not have to guess: `POST /accounts` tells you.** When the dossier\nis not yet complete the call is refused with `422\nACCOUNT_PROFILE_INCOMPLETE`, and the error carries a `missing` list naming\nevery field and document still needed, down to address components such as\n`business.registered_address.postal_code`. Once you have declared a person\nwho is a ≥25% owner or an authorised signer, it also reports what is missing\nfor that person specifically - `associated_persons[0].cpf`,\n`associated_persons[0].identity_document_or_approval`, and the person's own\ndocuments as `associated_persons[0].documents.selfie` and\n`associated_persons[0].documents.proof_of_address` (and, for a BRL account\nonly, the person's `pep_declaration`). Before that it asks\nfor the person itself (`associated_persons.ubo_25_plus`), so expect the list\nto get longer once you add one. That list is the\nauthoritative checklist for opening a Brazilian account, and it is\nlonger than `requirements.missing_fields` on the customer:\n`missing_fields` answers \"is this customer complete?\", the `422` answers \"can\nthis customer be given an account?\" Drive your onboarding UI off the `422`\nwhen the customer already exists, and off the tables above when you are still\nbuilding the create request.\n\nThe categories above are a declaration vocabulary, not a list of which\nartifact satisfies which category in a given country: whether a specific\nnational registry extract or utility bill is accepted is decided in review,\nand OZAV does not publish a per-jurisdiction acceptance table.\n\n**`approved` is not the finish line.** A customer can pass its checks and\nstill be refused on the money paths, because OZAV activates a customer only\nonce it also holds a payable identity. Today that means an account in\nBrazilian rails needs a CPF or CNPJ, so a customer whose tax identity is a\npassport can be created and verified but cannot be given a named\nBrazilian account.\n\n**There is no public field for that last step.** `status` goes no further\nthan `approved` - the values are `pending`, `in_review`, `approved`,\n`rejected` and `suspended`, and nothing else is ever returned. Do not poll\nfor a state that will not arrive. The way you find out is `POST /accounts`:\nit either opens the account or refuses with a coded error you can act on.\n\n### Jurisdictions we serve\n\nEligibility is decided by **declared residence and place of registration**,\nnever by nationality alone.\n\nCustomers whose tax identity or address is issued in one of these\njurisdictions are refused at creation, with `422 JURISDICTION_NOT_SERVED`\nnaming the field that caused it:\n\n| refused |\n|---|\n| `AF` Afghanistan, `BY` Belarus, `CU` Cuba, `IR` Iran, `KP` North Korea, `MM` Myanmar, `RU` Russia, `SD` Sudan, `SS` South Sudan, `SY` Syria, `ZW` Zimbabwe |\n\nThese are accepted and carry enhanced due diligence. Expect longer review\nand requests for further evidence:\n\n| enhanced due diligence |\n|---|\n| `AL` `BB` `BF` `CD` `CM` `GH` `HT` `JM` `JO` `ML` `MZ` `NG` `NI` `PA` `PH` `PK` `SN` `TT` `TZ` `UG` `VE` `VN` `YE` |\n\nEverywhere else is served, subject to name screening against the\nconsolidated UN, OFAC, UK and EU lists and to a risk-based decision. Being\noutside both tables means you may proceed; it does not by itself guarantee\nacceptance.\n"
    },
    {
      "name": "Verifications",
      "description": "**Identity and business verification for customers you onboard at OZAV.** Start checks for a\ncustomer (document, liveness, face match, proof of address, owners and more) and follow each outcome.\nOZAV uses the result in its own decision on the customer.\n"
    },
    {
      "name": "Quotes",
      "description": "**The price, before anything moves.** A quote returns OZAV's all-in rate for a corridor, with the\nexchange conditions as a spread in integer basis points, and you can add your own spread on top. Quotes\nare indicative: the rate is struck again when you create the transfer that references the quote.\n\n**Minimums.** Below the minimum for the fixed leg's currency a quote is refused with\n`400 AMOUNT_BELOW_MINIMUM`, and the message names the number that binds, so one refusal tells you what\nto send.\n\n**The BRL on-ramp floor is BRL 1,000.** Below it the order is refused, never reduced. The liquidity side\nsets its own minimum as BRL 1,000 sent or 200 USDT/USDC received; OZAV checks the BRL side because it is\nknown before the rate is struck, so BRL 1,000 is the only number you need. The floor can rise if that\nminimum rises, and it never falls below BRL 1,000. A BRL to BRS purchase has no FX leg, and its minimum\nis BRL 10.\n"
    },
    {
      "name": "Transfers",
      "description": "**One resource for every money movement.** On-ramp, off-ramp, FX and wallet sends all go through\n`POST /transfers`; the pair `(source.type, destination.type)` selects the flow, not a different URL. A\ncombination that is not served answers with a stable error code instead of a partial result. OZAV's\ncompliance and limit checks run on every transfer.\n\nCreating a transfer checks the minimum again, so `400 AMOUNT_BELOW_MINIMUM` can be raised here and not\nonly on the quote. See **Quotes** for the BRL on-ramp floor.\n"
    },
    {
      "name": "Transactions",
      "description": "**Every movement in one timeline:** the transfers you created through this API and the on-chain\ndeposits credited into custody, newest first.\n"
    },
    {
      "name": "Accounts",
      "description": "**Named accounts for your customers.** Receive funds into an `ozav_acc_` account that carries your\ncustomer's identity: in the customer's own name on Brazilian rails, and segregated and attributed to\nthe customer under OZAV on foreign-currency rails. Which currencies and rails are open depends on your organization, and the\n`Rail` schema gives each rail's state. Sandbox accounts are simulated; live opening is enabled per\norganization. The institution behind the account is never exposed.\n\n**Every live account needs the customer's documents, in every currency.** The list, the literal\n`doc_type` values and the upload sequence are in \"What each customer type must provide\" in the\n**Customers** tag. You do not have to guess it up front: `POST /accounts` refuses an incomplete dossier\nwith `422 ACCOUNT_PROFILE_INCOMPLETE` and a `missing` list naming what is absent.\n"
    },
    {
      "name": "Payout Destinations",
      "description": "**Where a customer's money lands when it leaves.** One resource for every payout rail, with a\nrail-keyed `details`: a Pix key in Brazil, a bank account abroad. A Pix destination must be in the\ncustomer's own name, unless OZAV has enabled third-party payouts for your organization; the other\nrails can pay a third party.\n"
    },
    {
      "name": "Custodial Wallets",
      "description": "**Balances OZAV holds for your customers.** OZAV holds the key and signs: list wallets, read balances\nand deposit addresses, and send. Custody is part of the regulated platform and\nneeds production access (KYB and a signed contract). For wallets your users control, see\n**Wallet as a Service**.\n"
    },
    {
      "name": "Swap",
      "description": "Quote a stablecoin conversion across the networks OZAV supports, with the network cost on its own\nline. This route quotes only; it does not execute.\n"
    },
    {
      "name": "Network Transfers",
      "description": "Move USDC you hold from one network to another, in the same wallet. The operation that moves value\nanswers `403 INSUFFICIENT_SCOPE` for every key; see the operation.\n"
    },
    {
      "name": "Cards",
      "description": "**Cards on your customers' balances.** Issue virtual cards with opaque `ozav_card_` ids, funded from\nthe customer's OZAV balance, and manage their lifecycle. Card data never travels in a plain response.\nThe sandbox issues cards end to end; live issuance is enabled per organization once the card program is\nactive.\n"
    },
    {
      "name": "Yield Overview",
      "x-displayName": "Overview",
      "description": "**Yield on stablecoins your users hold, without handing over the keys.** OZAV admits a short list of\nyield strategies, reads positions with their cost and realized performance on chain, and builds the\ndeposit and withdrawal transactions for your user's wallet to sign. OZAV never signs and never\nbroadcasts them.\n\n| | |\n|---|---|\n| Who holds the keys | Your user, for the deposit and withdrawal builds |\n| Who decides | You |\n| Risk | Protocol risk stays with whoever holds the funds |\n\n**The flow**\n\n1. `GET /yield/strategies` lists the strategies OZAV admits, each with a stable, opaque `id`.\n2. `POST /yield/preview` shows what a deposit would look like.\n3. `POST /yield/deposits/build` builds the deposit for your user to sign.\n4. `GET /yield/wallets/{address}/positions` reads the positions at the address.\n5. `POST /yield/withdrawals/build` builds the withdrawal.\n\n**Access.** Steps 1 and 2 need the `custody` product today; steps 3 to 5 need `self_custody`. Building\nis a closed pilot with two gates in series: while the environment gate is closed the builds answer\n`503 YIELD_BUILD_NOT_ENABLED`, and once it is open an organization that is not enrolled answers\n`403 YIELD_BUILD_NOT_IN_PILOT`. The routes in this section that read or apply yield under managed custody\nneed the `custody` product.\n"
    },
    {
      "name": "Yield",
      "description": "Strategies, previews, positions and transaction builds. Each strategy carries a stable, opaque `id`.\nThe underlying venue is never named, and its contract address appears only as the `to` of a\ntransaction built for you to sign.\n"
    },
    {
      "name": "Agents Overview",
      "x-displayName": "Overview",
      "description": "**AI agents that pay on behalf of a company, under a mandate the company approves.** A platform\nregisters agents under the grants companies gave it, and every payment request is checked against the\nmandate and recorded. A company can also run its own agents: it registers its platform with\n`first_party: true` and grants it a mandate like any other, with the same administrator approval.\n\n**A person approves once, when the company grants the mandate. Every action the mandate allows runs\nwithout human approval.** `human_approval_above_usd` is optional: leave it out and nothing waits for a\nperson. The threshold only ever applies to spending (payments and payouts): a funding or a beneficiary\nregistration inside the mandate never waits. A payout a person approved runs without a new request. An agent can pay resources (x402, MPP), pay a registered beneficiary in fiat currency, Pix in reais or a bank account in US dollars\n(`POST /agents/{agent_id}/payouts`) and ask for a Pix charge that becomes stablecoin in the company's\nwallet (`POST /agents/{agent_id}/fundings`, limited by `funding_limits`, never counted as spending). It can also\nregister the beneficiary it will pay (`POST /agents/{agent_id}/payees`), inside the `payee_rules` the company set, with no\napproval per registration. It cannot\ncreate a grant, widen its own mandate, move money of another account holder, or use a corridor outside\n`allowed_corridors`.\n\nThis product is in beta. In the sandbox the payment flow runs end to end with a simulated\nsignature; agent payouts and fundings run the same decision path (mandate, limits, approval\nthreshold, budget, webhooks) with simulated money: a fixed rate, a simulated transfer and a fake Pix\ncode, every receipt marked `simulated: true`; a registered payee gets a simulated id (`ozav_pd_sim_...`) that only the\nsandbox payouts of the agent that registered it accept. Finish a simulated payout or funding with\n`POST /agent-actions/{action_id}/simulate`. In production the\nmandate decides and records every request; until signing is switched on, a payment within the\nmandate answers `503 AGENT_PAYMENT_SIGNING_UNAVAILABLE` and nothing is signed.\n\nRefusals that come from the company's compliance state carry a fixed message\n(`AGENT_PAYMENT_NOT_PERMITTED`): the platform acts for the company and is not told why.\n\n**First ten minutes in the sandbox.** Use a test key (`ozav_sk_test_...`) with the `agents:write` and\n`agents:read` scopes. Nothing here moves real money.\n\n1. Register your organization as a platform: `POST /agent-platforms`. Share the returned `id` with the company.\n2. The company creates a grant once: in the OZAV portal, with multi-factor authentication, or with its own\n   admin key through `POST /agent-grants` (scope `agent_grants:write`), for itself or for one of its customers. The\n   mandate in it is the whole approval: its actions (`fiat_payout`, `fiat_funding`, `payee_registration`, ...), limits\n   (`per_payment_limit_usd`, `daily_limit_usd`, `total_budget_usd`, `recurring_budget`, `funding_limits`) and\n   `payee_rules`. Find it with `GET /agent-grants`.\n3. Register the agent under the grant: `POST /agents` with `grant` and a `label`.\n4. Register the beneficiary the agent will pay: `POST /agents/{agent_id}/payees` with an `idempotency_key`.\n   Inside `payee_rules` it is `executed` at once; the sandbox gives it a simulated id (`ozav_pd_sim_...`).\n5. Pay it: `POST /agents/{agent_id}/payouts`. Inside the mandate the response is `executed` with\n   `simulated: true`. Above `human_approval_above_usd` (if the company set one) it is `202 awaiting_approval`\n   until a person approves in the portal, and then it runs on its own.\n6. Ask for money to come in: `POST /agents/{agent_id}/fundings`. The response carries a simulated Pix code and\n   the status `awaiting_payment`.\n7. Finish what the sandbox cannot finish by itself: `POST /agent-actions/{action_id}/simulate` with\n   `{ \"outcome\": \"settled\" }` (the funding is paid and the stablecoin arrives) or `{ \"outcome\": \"returned\" }`\n   (the charge expired or the transfer came back, and the budget is released).\n8. Watch it all as webhooks: register an endpoint for `agent_action.updated` and every status change of the\n   actions above arrives once. `GET /agents/{agent_id}/actions` lists them.\n\nUse a new `idempotency_key` for each new request and the same one to repeat a request that did not answer. Try the\nrefusals too: a payout above `per_payment_limit_usd` answers `AGENT_PAYMENT_OUTSIDE_MANDATE` and is recorded.\n\n**How a site verifies an agent passport.** A site, an API or a payee that receives an agent's passport\n(`POST /agents/{agent_id}/passport`) needs no OZAV account and no API key to check it. Two ways:\n\n1. Offline, then a status read. The passport is an SD-JWT (`typ` `vc+sd-jwt`, signed `ES256`). Fetch OZAV's\n   public keys at `https://api.ozav.io/functions/v1/credencial-well-known/jwks.json` (cache them for minutes, not\n   days, so a key rotation reaches you), pick the key by the token's `kid`, and check the signature, `iss`\n   (`https://api.ozav.io`), `vct` (`ozav:agent-passport:v1`), `env` (`live`), `nbf` and `exp`. Each disclosure\n   the agent chose to show must hash to an entry of `_sd`. Then read the status at the token's `status.uri`\n   (`.../credential-status/{jti}`): only `valid` means the agent, its grant, its mandate and the company behind\n   it still hold right now. `revoked`, `expired` and `unknown` mean do not trust it.\n2. One call. `POST https://api.ozav.io/functions/v1/credencial-well-known/credentials/verify` with\n   `{ \"credential\": \"<the passport>\" }` checks the signature and the status together and answers `valid`, the\n   `status`, the agent (`subject`), the `environment` and the disclosed claims.\n\n```bash\ncurl -s https://api.ozav.io/functions/v1/credencial-well-known/credentials/verify \\\n  -H 'content-type: application/json' \\\n  -d '{\"credential\":\"<the passport the agent presented>\"}'\n```\n\nA sandbox passport (`vct` `ozav:agent-passport:sandbox:v1`, issuer ending in `/sandbox`) is never valid on these\nproduction paths. Test it with `.../credentials/verify-sandbox` and the keys at `.../sandbox-jwks.json`.\n"
    },
    {
      "name": "Agents",
      "description": "Platforms, grants, agents, passports, payments, payouts, fundings and exceptions. Every request is checked\nagainst the mandate the company approved, and recorded.\n"
    },
    {
      "name": "API Keys",
      "description": "Self-serve key lifecycle: create keys scoped to a subset of your\nentitlements, list them (prefixes only - secrets are shown once at\ncreation), rotate with an overlap window, and revoke. Test and live keys\ndiffer only by prefix; the environment is derived from it.\n"
    },
    {
      "name": "Statements",
      "description": "Your organization's monthly service statements: what OZAV charges or\ncredits you each month, with the itemized lines. The same statements the\ndashboard shows, readable with a key that holds `billing:read`.\n"
    },
    {
      "name": "Webhooks",
      "description": "Register HTTPS endpoints to receive signed event notifications. Each\nendpoint has its own signing secret (`ozav_whsec_`, shown once at create /\nrotate) and a list of subscribed event types. Endpoints are tenant-scoped.\n\n**Deliveries are also scoped by environment.** An event produced by a live\nkey is delivered only to endpoints registered in `live`, and a sandbox\nevent only to `test` endpoints. If you integrate in both, register one\nendpoint per environment. An endpoint created in the dashboard before it\nasked for an environment receives both: tell its events apart with the\nevent's `livemode`.\n\n**Verifying a delivery.** Every delivery carries three headers:\n\n```\nX-OZAV-Signature:   t=<unix>,v1=<hex>\nX-OZAV-Event-ID:    stable across retries, dedupe on it\nX-OZAV-Delivery-ID: unique per delivery attempt\n```\n\nParse `t` and `v1` out of `X-OZAV-Signature`, then recompute:\n\n```\nv1 == HMAC_SHA256(key = <your endpoint secret, WITH the ozav_whsec_ prefix>,\n                  message = \"<t>.\" + <raw request body>)\n```\n\nThree things break verification and are worth stating plainly, because each\none fails silently on your side:\n\n1. **The key is the whole secret string, prefix included.** `ozav_whsec_` is\n   part of the key, not a display label. Stripping it fails 100% of deliveries.\n2. **Sign the raw body bytes**, before any JSON parse. Re-serializing may or\n   may not reproduce the same bytes, so do not rely on it.\n3. **Compare against `v1` only**, not the whole `t=...,v1=...` header value.\n\nReject deliveries whose `t` is outside your tolerance window (five minutes is\nrecommended) and dedupe by `X-OZAV-Event-ID`.\n\n**When deliveries are not arriving**, read\n`GET /webhook-endpoints/{id}/deliveries` before debugging signatures. It\nreports `response_status` (what your server answered) and `error` (transport\nfailures). A `response_status` of `401` is a signature problem; a null\n`response_status` with an `error` is a network problem such as DNS or TLS,\nand no signature change will fix it.\n"
    },
    {
      "name": "Deprecated",
      "description": "**Routes that are leaving the API.** Each one keeps working for the integrations that use it until\nthe date in OZAV's notice to them, and its description says what to use instead. Do not build new\nintegrations on them.\n"
    }
  ],
  "x-tagGroups": [
    {
      "name": "Wallet as a Service",
      "tags": [
        "Wallet as a Service Overview",
        "Self-custody Wallets",
        "Vaults"
      ]
    },
    {
      "name": "Compliance as a Service",
      "tags": [
        "Compliance as a Service Overview",
        "Decisions",
        "Screenings",
        "Lists"
      ]
    },
    {
      "name": "Money Movement",
      "tags": [
        "Money Movement Overview",
        "Capabilities",
        "Customers",
        "Verifications",
        "Quotes",
        "Transfers",
        "Transactions",
        "Accounts",
        "Payout Destinations",
        "Custodial Wallets",
        "Swap",
        "Network Transfers",
        "Cards"
      ]
    },
    {
      "name": "Yield",
      "tags": [
        "Yield Overview",
        "Yield"
      ]
    },
    {
      "name": "Agents · Beta",
      "tags": [
        "Agents Overview",
        "Agents"
      ]
    },
    {
      "name": "Keys & Webhooks",
      "tags": [
        "API Keys",
        "Webhooks",
        "Statements"
      ]
    },
    {
      "name": "Deprecated",
      "tags": [
        "Deprecated"
      ]
    }
  ],
  "paths": {
    "/capabilities": {
      "get": {
        "operationId": "getCapabilities",
        "tags": [
          "Capabilities"
        ],
        "summary": "Get the caller's entitlements, key scopes, and feature readiness",
        "description": "Returns the two authorization axes for the presented key - the\norganization's `entitlements` and the key's `scopes` - plus the resolved\n`environment` and a per-feature readiness map (`live`, `beta`,\n`coming_soon`). Call it first in any integration and branch on the access\nit reports rather than hard-coding assumptions. Requires only a valid key\n(any entitlement, no specific scope).\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "responses": {
          "200": {
            "description": "The two authorization axes and per-feature availability.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entitlements": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Products enabled for the organization (e.g. liquidity, compliance, customers, cards, custody, self_custody, kyc_reliance)."
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Scopes granted to the presented API key."
                    },
                    "environment": {
                      "type": "string",
                      "description": "Which environment the presented key resolves to - `test` (sandbox) or `live` (production).",
                      "enum": [
                        "test",
                        "live"
                      ]
                    },
                    "features": {
                      "type": "object",
                      "description": "Per-feature readiness, keyed by feature name. Each value is one of\n`live` (generally available), `beta` (available to design partners,\nmay need enablement), or `coming_soon` (defined, and its write operations are refused).\n",
                      "additionalProperties": {
                        "type": "string",
                        "enum": [
                          "live",
                          "beta",
                          "coming_soon"
                        ]
                      }
                    },
                    "jurisdictions": {
                      "type": "object",
                      "description": "Where OZAV onboards customers, decided by declared residence\nand place of registration - never by nationality alone.\n\n`refused` is enforced at creation: a customer whose tax\nidentity or address is issued there is rejected with\n`422 JURISDICTION_NOT_SERVED`. `enhanced_due_diligence` is\naccepted, with longer review and further evidence requested.\n\nAnywhere else is served, subject to name screening and a\nrisk-based decision. Absence from both lists means you may\nproceed; it is not by itself a guarantee of acceptance.\n",
                      "properties": {
                        "refused": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "pattern": "^[A-Z]{2}$"
                          }
                        },
                        "enhanced_due_diligence": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "pattern": "^[A-Z]{2}$"
                          }
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "neobank_test_key": {
                    "summary": "A full-stack test key",
                    "value": {
                      "entitlements": [
                        "customers",
                        "compliance",
                        "liquidity",
                        "custody"
                      ],
                      "scopes": [
                        "customers:read",
                        "customers:write",
                        "quotes:write",
                        "accounts:read",
                        "accounts:write",
                        "transfers:write",
                        "transfers:read",
                        "destinations:write",
                        "screenings:write"
                      ],
                      "environment": "test",
                      "features": {
                        "quotes": "live",
                        "screenings": "live",
                        "transfers": "live",
                        "accounts": "live",
                        "verifications": "live",
                        "wallets": "live"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/capabilities \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/capabilities\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/capabilities\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers": {
      "post": {
        "operationId": "createCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Create a customer",
        "description": "Creates a customer - the anchor resource that quotes, verifications,\naccounts, payout destinations, wallets, and transfers all reference. Supply\n`type: individual` with an `individual` block (including\n`individual.tax_id`) or `type: business` with a `business` block\n(including `business.registration`); the required identity is enforced\nserver-side. The customer always starts `status: pending` and is verified\nseparately (see `POST /verifications`). Accepts an optional\n`Idempotency-Key` (recommended). Returns the created `Customer`.\n\nA customer already registered for your organization - through this\nAPI or through your onboarding link - is not created twice: the\nrequest returns 409 `CUSTOMER_ALREADY_EXISTS` (same tax identity)\nor `CUSTOMER_EMAIL_ALREADY_EXISTS` (same email), and `error.details`\ntells you which customer it is. When the existing customer was\ncreated through this API, `details` carries `existing_source: api`\nand `existing_id` - its `ozav_cus_` id in the same environment as\nyour key (use it with `GET /customers/{customer_id}`). `details` is omitted\nwhen that id cannot be resolved. When the existing customer came\nthrough your onboarding link, `details` carries\n`existing_source: onboarding_link` and `existing_id` (its customer\nid, or `null` while that registration is still under verification).\n\nThe record persists and reads back. Customer creation does\nnot open a real wallet or bank account by itself. In sandbox,\nverification simulation can materialize simulated wallet and BRL/Pix\naccount projections after approval; live provisioning requires\nenablement for your organization.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerCreateRequest"
              },
              "examples": {
                "individual": {
                  "summary": "Individual customer",
                  "value": {
                    "type": "individual",
                    "email": "ana@example.com",
                    "external_ref": "your-user-4821",
                    "individual": {
                      "legal_name": "Ana Ramos",
                      "date_of_birth": "1990-04-12",
                      "nationality": "BR",
                      "tax_id": {
                        "country": "BR",
                        "type": "cpf",
                        "value": "39053344705"
                      },
                      "residential_address": {
                        "line1": "Rua das Flores 100",
                        "city": "São Paulo",
                        "state": "SP",
                        "postal_code": "01000-000",
                        "country": "BR"
                      }
                    }
                  }
                },
                "business": {
                  "summary": "Business customer",
                  "value": {
                    "type": "business",
                    "email": "ops@acme.com.br",
                    "business": {
                      "legal_name": "Acme Pagamentos Ltda",
                      "registration": {
                        "country": "BR",
                        "type": "cnpj",
                        "value": "11222333000181"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Customer created.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "examples": {
                  "individual_pending": {
                    "summary": "Created - always starts pending",
                    "value": {
                      "id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "customer",
                      "type": "individual",
                      "status": "pending",
                      "external_ref": "your-user-4821",
                      "email": "ana@example.com",
                      "individual": {
                        "legal_name": "Ana Ramos",
                        "date_of_birth": "1990-04-12",
                        "nationality": "BR",
                        "tax_id": {
                          "country": "BR",
                          "type": "cpf",
                          "value": "39053344705"
                        }
                      },
                      "verification": {
                        "mode": "full",
                        "status": "pending",
                        "checks": {}
                      },
                      "requirements": {
                        "due": [],
                        "missing_fields": [
                          "phone",
                          "residential_address",
                          "source_of_funds",
                          "expected_monthly_volume_usd",
                          "monthly_income_usd",
                          "occupation",
                          "is_pep",
                          "account_purpose"
                        ]
                      },
                      "created_at": "2026-07-12T18:00:00Z",
                      "updated_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/IdempotencyProcessing"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "description": "The customer was persisted but its public representation could not be produced (`CUSTOMER_MAPPING_FAILED`). This is now narrow: it fires only when a field the schema marks required cannot be produced. A record that is merely incomplete is returned with its optional blocks omitted instead (see `CustomerVerification.mode`), so one incomplete record never fails a call. The record may exist - reconcile with `GET /customers` before creating another.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"type\": \"individual\",\n    \"email\": \"ana@example.com\",\n    \"external_ref\": \"your-user-4821\",\n    \"individual\": {\n      \"legal_name\": \"Ana Ramos\",\n      \"date_of_birth\": \"1990-04-12\",\n      \"nationality\": \"BR\",\n      \"tax_id\": {\n        \"country\": \"BR\",\n        \"type\": \"cpf\",\n        \"value\": \"39053344705\"\n      },\n      \"residential_address\": {\n        \"line1\": \"Rua das Flores 100\",\n        \"city\": \"São Paulo\",\n        \"state\": \"SP\",\n        \"postal_code\": \"01000-000\",\n        \"country\": \"BR\"\n      }\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"type\": \"individual\",\n    \"email\": \"ana@example.com\",\n    \"external_ref\": \"your-user-4821\",\n    \"individual\": {\n      \"legal_name\": \"Ana Ramos\",\n      \"date_of_birth\": \"1990-04-12\",\n      \"nationality\": \"BR\",\n      \"tax_id\": {\n        \"country\": \"BR\",\n        \"type\": \"cpf\",\n        \"value\": \"39053344705\"\n      },\n      \"residential_address\": {\n        \"line1\": \"Rua das Flores 100\",\n        \"city\": \"São Paulo\",\n        \"state\": \"SP\",\n        \"postal_code\": \"01000-000\",\n        \"country\": \"BR\"\n      }\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"type\": \"individual\",\n        \"email\": \"ana@example.com\",\n        \"external_ref\": \"your-user-4821\",\n        \"individual\": {\n            \"legal_name\": \"Ana Ramos\",\n            \"date_of_birth\": \"1990-04-12\",\n            \"nationality\": \"BR\",\n            \"tax_id\": {\n                \"country\": \"BR\",\n                \"type\": \"cpf\",\n                \"value\": \"39053344705\",\n            },\n            \"residential_address\": {\n                \"line1\": \"Rua das Flores 100\",\n                \"city\": \"São Paulo\",\n                \"state\": \"SP\",\n                \"postal_code\": \"01000-000\",\n                \"country\": \"BR\",\n            },\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listCustomers",
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "description": "Lists your customers, newest first, using the standard cursor pagination\n(`limit`, `starting_after`). Returns a `Page` whose `data` is an array of\n`Customer`.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated customers.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Customer"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of customers",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "customer",
                          "type": "individual",
                          "status": "approved",
                          "email": "ana@example.com",
                          "individual": {
                            "legal_name": "Ana Ramos",
                            "tax_id": {
                              "country": "BR",
                              "type": "cpf",
                              "value": "39053344705"
                            }
                          },
                          "verification": {
                            "mode": "full",
                            "status": "approved",
                            "checks": {}
                          },
                          "requirements": {
                            "due": [],
                            "missing_fields": [
                              "phone",
                              "residential_address",
                              "source_of_funds",
                              "expected_monthly_volume_usd",
                              "monthly_income_usd",
                              "occupation",
                              "is_pep",
                              "account_purpose"
                            ]
                          },
                          "created_at": "2026-07-12T18:00:00Z",
                          "updated_at": "2026-07-12T18:05:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null,
                      "still_required": [
                        "company_registration",
                        "ownership_structure",
                        "source_of_funds"
                      ]
                    }
                  },
                  "incomplete": {
                    "summary": "A record OZAV cannot fully represent, alongside a complete one",
                    "description": "The second record has no recorded verification posture, so `verification.mode`, `individual` and `business` are omitted and `requirements.missing_fields` names what is still needed. Note the first record is unaffected: one incomplete record never fails the call, and its `status` must not be read as a verification attestation while `mode` is absent.\n",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "customer",
                          "type": "individual",
                          "status": "approved",
                          "email": "ana@example.com",
                          "individual": {
                            "legal_name": "Ana Ramos",
                            "tax_id": {
                              "country": "BR",
                              "type": "cpf",
                              "value": "39053344705"
                            }
                          },
                          "verification": {
                            "mode": "full",
                            "status": "approved",
                            "checks": {}
                          },
                          "requirements": {
                            "due": [],
                            "missing_fields": [
                              "phone",
                              "residential_address",
                              "source_of_funds",
                              "expected_monthly_volume_usd",
                              "monthly_income_usd",
                              "occupation",
                              "is_pep",
                              "account_purpose"
                            ]
                          },
                          "created_at": "2026-07-12T18:00:00Z",
                          "updated_at": "2026-07-12T18:05:00Z"
                        },
                        {
                          "id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V2",
                          "object": "customer",
                          "type": "business",
                          "status": "approved",
                          "email": "contato@example.com",
                          "verification": {
                            "status": "approved",
                            "checks": {}
                          },
                          "requirements": {
                            "due": [],
                            "missing_fields": [
                              "legal_name",
                              "registration",
                              "registered_address"
                            ]
                          },
                          "created_at": "2026-07-12T18:10:00Z",
                          "updated_at": "2026-07-12T18:10:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/terms-sessions": {
      "post": {
        "operationId": "createTermsSession",
        "tags": [
          "Customers"
        ],
        "summary": "Create a terms session",
        "description": "Creates a terms-of-service session. Customer provisioning at a settlement\npartner is fail-closed on an accepted terms session: `POST /customers`\nreturns `422 TERMS_ACCEPTANCE_REQUIRED` until you submit the\n`terms_session_id` of a session that has been accepted.\n\nThe response carries `id` - the session token - **once, at creation**.\nOZAV stores only its hash, so it is never returned again. Persist it\nbefore you present the terms to your end user.\n\nPresent `terms_url` to the end user, collect their acceptance in your own\nflow, then record it with `POST /customers/terms-sessions/accept`.\nSessions expire; `expires_at` is absolute.\n\nAccepts an optional `Idempotency-Key`. Replaying the same key returns the\nsame session **without** `id` - the token is only ever issued once.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "responses": {
          "200": {
            "description": "Idempotent replay - the same session, without the token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TermsSession"
                }
              }
            }
          },
          "201": {
            "description": "Terms session created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TermsSession"
                },
                "examples": {
                  "created": {
                    "summary": "Created - token returned once",
                    "value": {
                      "id": "ts_8Kd2mQ4vR7xN1pL5wT3yB6zA9cF0hJ2s",
                      "object": "terms_session",
                      "status": "pending",
                      "terms_url": "https://ozav.io/terms",
                      "terms_version": "2.0",
                      "expires_at": "2026-09-03T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/terms-sessions \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/terms-sessions\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/terms-sessions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/terms-sessions/accept": {
      "post": {
        "operationId": "acceptTermsSession",
        "tags": [
          "Customers"
        ],
        "summary": "Record acceptance of a terms session",
        "description": "Records that your end user accepted the terms of the session, together\nwith the evidence of that acceptance. You collect the acceptance in your\nown flow; this call is your attestation of it, and OZAV stores it against\nthe terms version that was in force when the session was created.\n\nIdempotent: repeating the call returns the same result and never\noverwrites the original evidence. The session token is **not** returned.\n\nAfter a `200`, submit the same `terms_session_id` to `POST /customers`.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TermsSessionAcceptRequest"
              },
              "examples": {
                "accept": {
                  "summary": "Attest the acceptance you collected",
                  "value": {
                    "terms_session_id": "ts_8Kd2mQ4vR7xN1pL5wT3yB6zA9cF0hJ2s",
                    "accepted_by": {
                      "legal_name": "Ana Ramos",
                      "email": "ana@example.com"
                    },
                    "accepted_at": "2026-09-02T17:41:09Z",
                    "ip_address": "203.0.113.42",
                    "user_agent": "Mozilla/5.0"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acceptance recorded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TermsSession"
                },
                "examples": {
                  "accepted": {
                    "summary": "Accepted",
                    "value": {
                      "object": "terms_session",
                      "status": "accepted",
                      "terms_url": "https://ozav.io/terms",
                      "terms_version": "2.0",
                      "expires_at": "2026-09-03T18:00:00Z",
                      "accepted_at": "2026-09-02T17:41:09Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/terms-sessions/accept \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"terms_session_id\": \"ts_8Kd2mQ4vR7xN1pL5wT3yB6zA9cF0hJ2s\",\n    \"accepted_by\": {\n      \"legal_name\": \"Ana Ramos\",\n      \"email\": \"ana@example.com\"\n    },\n    \"accepted_at\": \"2026-09-02T17:41:09Z\",\n    \"ip_address\": \"203.0.113.42\",\n    \"user_agent\": \"Mozilla/5.0\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/terms-sessions/accept\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"terms_session_id\": \"ts_8Kd2mQ4vR7xN1pL5wT3yB6zA9cF0hJ2s\",\n    \"accepted_by\": {\n      \"legal_name\": \"Ana Ramos\",\n      \"email\": \"ana@example.com\"\n    },\n    \"accepted_at\": \"2026-09-02T17:41:09Z\",\n    \"ip_address\": \"203.0.113.42\",\n    \"user_agent\": \"Mozilla/5.0\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/terms-sessions/accept\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"terms_session_id\": \"ts_8Kd2mQ4vR7xN1pL5wT3yB6zA9cF0hJ2s\",\n        \"accepted_by\": {\n            \"legal_name\": \"Ana Ramos\",\n            \"email\": \"ana@example.com\",\n        },\n        \"accepted_at\": \"2026-09-02T17:41:09Z\",\n        \"ip_address\": \"203.0.113.42\",\n        \"user_agent\": \"Mozilla/5.0\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "get": {
        "operationId": "getCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Retrieve a customer",
        "description": "Retrieves a single customer by its `ozav_cus_` id, including its current\n`status`, `verification` posture, and outstanding `requirements`. An id\nthat does not exist or belongs to another organization returns `404`.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "examples": {
                  "approved": {
                    "summary": "A verified individual customer",
                    "value": {
                      "id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "customer",
                      "type": "individual",
                      "status": "approved",
                      "external_ref": "your-user-4821",
                      "email": "ana@example.com",
                      "individual": {
                        "legal_name": "Ana Ramos",
                        "date_of_birth": "1990-04-12",
                        "nationality": "BR",
                        "tax_id": {
                          "country": "BR",
                          "type": "cpf",
                          "value": "39053344705"
                        }
                      },
                      "verification": {
                        "mode": "full",
                        "status": "approved",
                        "checks": {
                          "kyc_document": {
                            "outcome": "pass",
                            "verified_at": "2026-07-12T18:04:00Z"
                          }
                        }
                      },
                      "requirements": {
                        "due": [],
                        "missing_fields": [
                          "phone",
                          "residential_address",
                          "source_of_funds",
                          "expected_monthly_volume_usd",
                          "monthly_income_usd",
                          "occupation",
                          "is_pep",
                          "account_purpose"
                        ]
                      },
                      "created_at": "2026-07-12T18:00:00Z",
                      "updated_at": "2026-07-12T18:05:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "patch": {
        "operationId": "updateCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Update a customer",
        "description": "Updates a customer's mutable attributes (contact fields, identity blocks,\nmetadata). Only the fields you include are changed; omitted fields are\nleft untouched. Returns the updated `Customer`.\n\n**Customers registered outside the API.** A customer registered through\nyour public registration link, or created from the onboarding documents,\naccepts only the business declarations through this endpoint:\n`business.expected_daily_volume_usd`,\n`business.expected_monthly_volume_usd`, `business.annual_revenue_usd`,\n`business.source_of_funds`, `business.nature_of_business`,\n`business.website`, `business.account_purpose`,\n`business.incorporation_date`, `business.bc_protege_disabled` and\n`business.account_opening_terms_accepted_by`, and the phone (`business.phone`, with its `country_code` and `number`) so the account-opening requirements can be met. Your organization's own\naccount accepts only `business.bc_protege_disabled` and\n`business.account_opening_terms_accepted_by` (its company declarations\ncome from your organization's onboarding). Any other field returns\n`409 LEGACY_PROFILE_DECLARATIONS_ONLY` and nothing is written. If\nanother change to the customer lands at the same time, the call\nreturns `409 CONCURRENT_UPDATE`; retrying the same request is safe.\nThe response does not return the declared values for these customers,\nand its `requirements.missing_fields` is not recalculated from them.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerUpdateRequest"
              },
              "examples": {
                "contact_update": {
                  "summary": "Update contactable fields",
                  "value": {
                    "email": "ana.ramos@example.com",
                    "metadata": {
                      "tier": "gold"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "examples": {
                  "updated": {
                    "summary": "Contact email and metadata updated",
                    "value": {
                      "id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "customer",
                      "type": "individual",
                      "status": "approved",
                      "email": "ana.ramos@example.com",
                      "individual": {
                        "legal_name": "Ana Ramos",
                        "tax_id": {
                          "country": "BR",
                          "type": "cpf",
                          "value": "39053344705"
                        }
                      },
                      "verification": {
                        "mode": "full",
                        "status": "approved",
                        "checks": {}
                      },
                      "requirements": {
                        "due": [],
                        "missing_fields": [
                          "phone",
                          "residential_address",
                          "source_of_funds",
                          "expected_monthly_volume_usd",
                          "monthly_income_usd",
                          "occupation",
                          "is_pep",
                          "account_purpose"
                        ]
                      },
                      "created_at": "2026-07-12T18:00:00Z",
                      "updated_at": "2026-07-12T18:10:00Z",
                      "metadata": {
                        "tier": "gold"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"email\": \"ana.ramos@example.com\",\n    \"metadata\": {\n      \"tier\": \"gold\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"email\": \"ana.ramos@example.com\",\n    \"metadata\": {\n      \"tier\": \"gold\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"email\": \"ana.ramos@example.com\",\n        \"metadata\": {\n            \"tier\": \"gold\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/associated-persons": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "post": {
        "operationId": "createAssociatedPerson",
        "tags": [
          "Customers"
        ],
        "summary": "Add an associated person (UBO, director, officer, or signer) to a business customer",
        "description": "Adds an associated person - a UBO, director, officer, or authorized\nsigner - to a business customer, for KYB. Supply one or more `roles` and\nthe person's `individual` identity block (and `ownership_percent` for\nowners). Valid only for `business` customers. Returns the created\n`AssociatedPerson`.\n\n**Gender.** `individual.gender` is recorded with the same rules as\n`PATCH` and the customer portal. If the customer already has this\nperson (same CPF) with another declared gender and the gender request\nis closed, the call returns `409 GENDER_ALREADY_DECLARED` and the\nperson is not created. Otherwise the value also applies to the\ncustomer's other records of the same person.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssociatedPersonCreateRequest"
              },
              "examples": {
                "ubo": {
                  "summary": "A 40%-owning UBO who is also a director",
                  "value": {
                    "roles": [
                      "ubo",
                      "director"
                    ],
                    "ownership_percent": 40,
                    "individual": {
                      "legal_name": "Carlos Souza",
                      "tax_id": {
                        "country": "BR",
                        "type": "cpf",
                        "value": "39053344705"
                      }
                    },
                    "email": "carlos@acme.com.br"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Associated person created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssociatedPerson"
                },
                "examples": {
                  "ubo": {
                    "summary": "A 40%-owning UBO who is also a director",
                    "value": {
                      "id": "ozav_ap_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "associated_person",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "roles": [
                        "ubo",
                        "director"
                      ],
                      "ownership_percent": 40,
                      "individual": {
                        "legal_name": "Carlos Souza",
                        "tax_id": {
                          "country": "BR",
                          "type": "cpf",
                          "value": "39053344705"
                        }
                      },
                      "email": "carlos@acme.com.br",
                      "verification": {
                        "mode": "full",
                        "status": "pending",
                        "checks": {}
                      },
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"roles\": [\n      \"ubo\",\n      \"director\"\n    ],\n    \"ownership_percent\": 40,\n    \"individual\": {\n      \"legal_name\": \"Carlos Souza\",\n      \"tax_id\": {\n        \"country\": \"BR\",\n        \"type\": \"cpf\",\n        \"value\": \"39053344705\"\n      }\n    },\n    \"email\": \"carlos@acme.com.br\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"roles\": [\n      \"ubo\",\n      \"director\"\n    ],\n    \"ownership_percent\": 40,\n    \"individual\": {\n      \"legal_name\": \"Carlos Souza\",\n      \"tax_id\": {\n        \"country\": \"BR\",\n        \"type\": \"cpf\",\n        \"value\": \"39053344705\"\n      }\n    },\n    \"email\": \"carlos@acme.com.br\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"roles\": [\n            \"ubo\",\n            \"director\",\n        ],\n        \"ownership_percent\": 40,\n        \"individual\": {\n            \"legal_name\": \"Carlos Souza\",\n            \"tax_id\": {\n                \"country\": \"BR\",\n                \"type\": \"cpf\",\n                \"value\": \"39053344705\",\n            },\n        },\n        \"email\": \"carlos@acme.com.br\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listAssociatedPersons",
        "tags": [
          "Customers"
        ],
        "summary": "List a customer's associated persons",
        "description": "Lists the associated persons of a business customer, using the standard\ncursor pagination. Returns a `Page` of `AssociatedPerson`.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Associated persons.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AssociatedPerson"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of associated persons",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_ap_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "associated_person",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "roles": [
                            "ubo",
                            "director"
                          ],
                          "ownership_percent": 40,
                          "individual": {
                            "legal_name": "Carlos Souza",
                            "tax_id": {
                              "country": "BR",
                              "type": "cpf",
                              "value": "39053344705"
                            }
                          },
                          "verification": {
                            "mode": "full",
                            "status": "pending",
                            "checks": {}
                          },
                          "created_at": "2026-07-12T18:00:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/associated-persons/{person_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        },
        {
          "name": "person_id",
          "in": "path",
          "required": true,
          "description": "The opaque associated-person id (`ozav_ap_...`).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "operationId": "updateAssociatedPerson",
        "tags": [
          "Customers"
        ],
        "summary": "Update an associated person",
        "description": "Updates an associated person's mutable attributes (roles, ownership\npercentage, identity block, e-mail). Only included fields are changed, inside\n`individual` too: omitted identity fields keep their stored value.\nReturns the updated `AssociatedPerson`.\n\n**Gender.** `individual.gender` (`male`, `female` or `other`) is\nrecorded with the same rules as the customer portal. The open\ngender request on the customer closes only when every person who\nsigns for the company has a gender. Once that request is closed, a\ndeclared gender is locked: changing it returns `409\nGENDER_ALREADY_DECLARED`.\n\n**Records from onboarding documents.** An associated person created\nfrom the customer's onboarding documents accepts only\n`individual.gender`, `individual.phone`, `email` and, when the\ndocuments did not bring them, `individual.residential_address` and\n`individual.date_of_birth` through the API; any other field returns\n`409 LEGACY_RECORD_GENDER_ONLY`. The address and the date of birth can\nonly be filled in. An address counts as already on record when it is\ncomplete (street, number, neighborhood, city, state and postal code),\nwhether the stored record or the documents hold it; an incomplete one\nis completed by merging: what is already on record survives and only\nits blank components are taken from the call. A date of birth counts\nas soon as one exists. Replacing what is already on record returns\n`409 LEGACY_RECORD_FIELD_ALREADY_SET`, with the first such field in\n`param` and all of them in `details`. The call is refused as a whole\nand nothing in the body is written, so resend without those fields;\nthe correction itself goes through OZAV support. Send the whole\n`residential_address`, not a part of it (a Brazilian one needs a\npostal code with 8 digits and a 2-letter state). A retry after a lost\nresponse may answer `409 LEGACY_RECORD_FIELD_ALREADY_SET` because the\nfirst call was applied. With `gender` in the body, a fill that lands\nbetween the checks can be refused after the gender was recorded:\nresend without `gender`. These records do not return an `individual`\nblock, the phone or the e-mail, and the other identity fields (legal name, tax id)\nlisted in their `requirements.missing_fields` are completed through\nOZAV support, not through this endpoint. For them `requirements` is\ncomputed from a person without an `individual` block, so it keeps\nlisting `individual.phone` after you set it; the address, the date of\nbirth and `email` leave the list once they are set.\n\n**Concurrent changes.** If the person changes between read and\nwrite, the call returns `409 CONCURRENT_UPDATE`; retrying the same\nrequest is safe.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssociatedPersonUpdateRequest"
              },
              "examples": {
                "ownership": {
                  "summary": "Revise ownership",
                  "value": {
                    "ownership_percent": 55
                  }
                },
                "gender": {
                  "summary": "Declare the gender of a signer",
                  "value": {
                    "individual": {
                      "gender": "female"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated associated person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssociatedPerson"
                },
                "examples": {
                  "updated": {
                    "summary": "Ownership revised to 55%",
                    "value": {
                      "id": "ozav_ap_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "associated_person",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "roles": [
                        "ubo",
                        "director"
                      ],
                      "ownership_percent": 55,
                      "individual": {
                        "legal_name": "Carlos Souza",
                        "tax_id": {
                          "country": "BR",
                          "type": "cpf",
                          "value": "39053344705"
                        }
                      },
                      "verification": {
                        "mode": "full",
                        "status": "pending",
                        "checks": {}
                      },
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons/ozav_ap_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"ownership_percent\": 55\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons/ozav_ap_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"ownership_percent\": 55\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/associated-persons/ozav_ap_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"ownership_percent\": 55,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "post": {
        "operationId": "createCustomerDocument",
        "tags": [
          "Customers"
        ],
        "summary": "Start a supporting-document upload for a customer",
        "description": "Declares a KYC/KYB document and returns a short-lived\n`upload_url` the client PUTs the file bytes to. Documents may belong to\nthe customer itself or to one of its associated persons. Use generic\ndocument categories such as `company_registration`, `identity_document`,\n`selfie`, `liveness`, `proof_of_address`, `ownership_structure`,\n`financial_statement`, and `source_of_funds`.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerDocumentUploadRequest"
              },
              "examples": {
                "proof_of_address": {
                  "summary": "Declare a proof-of-address PDF",
                  "value": {
                    "doc_type": "proof_of_address",
                    "mime": "application/pdf"
                  }
                },
                "associated_person_identity_front": {
                  "summary": "Declare a representative identity document",
                  "value": {
                    "subject_type": "associated_person",
                    "associated_person_id": "ozav_ap_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "doc_type": "identity_document",
                    "side": "front",
                    "issuing_country": "BR",
                    "mime": "image/jpeg"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload grant created; PUT the file bytes to `upload_url`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDocumentUpload"
                },
                "examples": {
                  "created": {
                    "summary": "A pending-upload document with its signed URL",
                    "value": {
                      "id": "ozav_doc_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "document",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "subject_type": "customer",
                      "doc_type": "proof_of_address",
                      "side": null,
                      "issuing_country": null,
                      "status": "pending_upload",
                      "mime": "application/pdf",
                      "size_bytes": null,
                      "sha256": null,
                      "created_at": "2026-07-12T18:00:00Z",
                      "updated_at": "2026-07-12T18:00:00Z",
                      "upload_url": "https://storage.ozav.io/upload/ozav_cus_01J8.../ozav_doc_01J8...",
                      "upload_expires_at": "2026-07-12T20:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"doc_type\": \"proof_of_address\",\n    \"mime\": \"application/pdf\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"doc_type\": \"proof_of_address\",\n    \"mime\": \"application/pdf\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"doc_type\": \"proof_of_address\",\n        \"mime\": \"application/pdf\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listCustomerDocuments",
        "tags": [
          "Customers"
        ],
        "summary": "List a customer's supporting documents",
        "description": "Lists a customer's uploaded supporting documents, using the standard cursor\npagination. Returns a `Page` of `CustomerDocument`. Never returns the raw\nstorage location or a download URL.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Customer documents.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustomerDocument"
                          }
                        },
                        "still_required_by_person": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PendingDocumentsOfPerson"
                          },
                          "description": "What each associated person still owes. A Brazilian business owes, per ≥25% owner or signer, `identity_document`, `selfie` (or `liveness`) and `proof_of_address`; upload them with `subject_type: associated_person`. `pep_declaration` is optional and never listed here (only a BRL account asks for it, in the `POST /accounts` `missing` list). One entry per person (empty `still_required` when that person is complete). `[]` when this customer has no per-person requirement (individuals, non-Brazilian businesses) - and also when a Brazilian business has not declared any associated person yet: declare them first, then this list fills in (`POST /accounts` keeps asking for `associated_persons.ubo_25_plus` meanwhile). Omitted, like `still_required`, when it could not be measured. `POST /accounts` refuses with the same names, `associated_persons[i].documents.<doc_type>`."
                        },
                        "still_required": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustomerDocumentType"
                          },
                          "description": "The document categories this customer still owes, computed over all of its documents rather than the page you are reading, so paging does not change the answer. A document counts only once its bytes have arrived: one you declared but never uploaded, and one that failed review, both still appear here.\n\n**Read its absence carefully. An empty array and a missing field mean opposite things.** An empty array says the customer owes nothing. The field is omitted when OZAV does not model a required set for this customer, which today means only a `type` that is neither `individual` nor `business`. Individuals and businesses of any country are modelled. Omitted never means \"nothing is required\": it means we will not guess. It is also omitted if the summary could not be computed, so that a failed read can never be read as a clean bill.\n\nThe set depends on the customer and the jurisdiction. An individual owes a photo ID and a proof of address (plus a tax return above the high-volume threshold). A business owes its incorporation papers, and its ownership registry where the country and legal form keep them as separate documents. A business registered in Brazil additionally owes the financial pack the local account rail requires.\nThis is guidance, not the gate. The `422` from account opening remains the authoritative checklist, and this list is built to never ask for less than that `422` will."
                        },
                        "pending": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PendingDocument"
                          },
                          "description": "The same categories as `still_required`, in the same order, each carrying why it is still pending. `still_required` is unchanged and is not deprecated, so an existing integration needs no edit.\n\n**Use this one when you resend.** `still_required` cannot tell \"never arrived\" from \"arrived and was rejected\", and those ask for opposite actions: the first means upload, the second means fix the document and upload again. Reading only `still_required`, an integrator resends the same rejected file over and over.\n\nOmitted under exactly the same conditions as `still_required`, and for the same reason."
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of documents",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_doc_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "document",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "doc_type": "proof_of_address",
                          "status": "received",
                          "mime": "application/pdf",
                          "size_bytes": 20480,
                          "sha256": "b9f3a1c2d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f",
                          "created_at": "2026-07-12T18:00:00Z",
                          "updated_at": "2026-07-12T18:05:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null,
                      "still_required": [
                        "proof_of_address",
                        "bank_statement"
                      ],
                      "pending": [
                        {
                          "doc_type": "proof_of_address",
                          "state": "rejected",
                          "reason": "Issued more than 90 days ago."
                        },
                        {
                          "doc_type": "bank_statement",
                          "state": "missing"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/documents/{document_id}/upload": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        },
        {
          "$ref": "#/components/parameters/DocumentId"
        }
      ],
      "put": {
        "operationId": "uploadCustomerDocumentBytes",
        "tags": [
          "Customers"
        ],
        "summary": "Upload supporting-document bytes",
        "description": "Uploads file bytes to the short-lived OZAV-hosted `upload_url`\nreturned by `POST /customers/{customer_id}/documents`. Send the same\nAPI key used to create the grant. OZAV stores the file in private\nstorage, recomputes size and SHA-256 server-side, and moves the\ndocument to `received` when the bytes are accepted.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "upload_token",
            "in": "query",
            "required": true,
            "description": "Short-lived upload token embedded in the `upload_url`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/heic": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document bytes accepted and the document is marked received.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDocument"
                },
                "examples": {
                  "received": {
                    "summary": "A received supporting document",
                    "value": {
                      "id": "ozav_doc_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "document",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "doc_type": "proof_of_address",
                      "status": "received",
                      "mime": "application/pdf",
                      "size_bytes": 2048,
                      "sha256": "5f70bf18a08600701687fb0d4f009dc7bbdc9e2b0e7f2d7d1e04a1fbf30abbb5",
                      "created_at": "2026-07-12T18:00:00Z",
                      "updated_at": "2026-07-12T18:02:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents/{document_id}/upload \\\n  -X PUT \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents/{document_id}/upload\", {\n  method: \"PUT\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.put(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents/{document_id}/upload\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/deactivate": {
      "post": {
        "operationId": "deactivateCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Deactivate a customer",
        "description": "Stops new money movement for the customer. Card purchases are declined with `account_inactive`. New cards, transfers (`POST /transfers`), account payouts and wallet sends are refused with `403 CUSTOMER_DEACTIVATED`; withdrawals through the custody API are refused with `403 USER_DEACTIVATED`. Deposits sent to the customer's wallets and accounts still arrive and are credited, but cannot be spent until you reactivate. It applies to test keys too, so you can try it in the sandbox.\n\nOperations created before the deactivation finish, except wallet sends still waiting for approval or a delay, which are refused when they would execute. Self-custody transaction intents are not affected: OZAV does not sign them.\n\nCalling it again on a deactivated customer changes nothing and returns the date of the first deactivation. This is your own switch: it does not lift, and does not reveal, any restriction OZAV applies for compliance reasons. Your organization's own account cannot be deactivated (`422 CUSTOMER_DEACTIVATION_NOT_ALLOWED`).",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer's deactivation state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDeactivation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/reactivate": {
      "post": {
        "operationId": "reactivateCustomer",
        "tags": [
          "Customers"
        ],
        "summary": "Reactivate a customer",
        "description": "Clears your deactivation, so the customer can move money again. It only clears what `POST /customers/{customer_id}/deactivate` set: a restriction OZAV applies for compliance reasons stays in force, and `deactivated: false` here does not mean there is none. Calling it on an active customer returns the same state.",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer's deactivation state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDeactivation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/reactivate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/reactivate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/reactivate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/deactivation": {
      "get": {
        "operationId": "getCustomerDeactivation",
        "tags": [
          "Customers"
        ],
        "summary": "Get a customer's deactivation state",
        "description": "Whether you have deactivated this customer, and since when.",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The customer's deactivation state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDeactivation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivation \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivation\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivation\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/destination-identifications": {
      "post": {
        "operationId": "createDestinationIdentification",
        "tags": [
          "Customers"
        ],
        "summary": "Identify who holds a destination wallet",
        "description": "Records who holds a wallet the customer sent crypto to: the customer's own self-hosted wallet (with the ownership attestation), or the customer's account at an exchange. OZAV asks for it with a `requirement.due` webhook (`reason: destination_unidentified`) and a `documents_due` item with `purpose: travel_rule`; answering closes that item. You can also send it before being asked. The identification applies to every send of the customer to the same wallet. It does not allow or block any send.",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DestinationIdentificationRequest"
              },
              "examples": {
                "exchange": {
                  "summary": "The customer's account at an exchange",
                  "value": {
                    "chain": "tron",
                    "address": "TGssfnTouNs2H9AYPRkeZo9zuRxTPB5KCq",
                    "wallet_type": "external",
                    "exchange": {
                      "name": "Example Exchange"
                    }
                  }
                },
                "self_custodied": {
                  "summary": "The customer's own wallet",
                  "value": {
                    "chain": "tron",
                    "address": "TGssfnTouNs2H9AYPRkeZo9zuRxTPB5KCq",
                    "wallet_type": "self_custodied",
                    "wallet_attested_ownership_at": "2026-10-08T12:00:00Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The identification, as recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DestinationIdentification"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/destination-identifications \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"chain\": \"tron\",\n    \"address\": \"TGssfnTouNs2H9AYPRkeZo9zuRxTPB5KCq\",\n    \"wallet_type\": \"external\",\n    \"exchange\": {\n      \"name\": \"Example Exchange\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/destination-identifications\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"chain\": \"tron\",\n    \"address\": \"TGssfnTouNs2H9AYPRkeZo9zuRxTPB5KCq\",\n    \"wallet_type\": \"external\",\n    \"exchange\": {\n      \"name\": \"Example Exchange\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/destination-identifications\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"chain\": \"tron\",\n        \"address\": \"TGssfnTouNs2H9AYPRkeZo9zuRxTPB5KCq\",\n        \"wallet_type\": \"external\",\n        \"exchange\": {\n            \"name\": \"Example Exchange\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listDestinationIdentifications",
        "tags": [
          "Customers"
        ],
        "summary": "List a customer's destination identifications",
        "description": "The wallets identified for this customer, newest first.",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The identifications.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DestinationIdentificationList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/destination-identifications \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/destination-identifications\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/destination-identifications\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/limit-requests": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "post": {
        "operationId": "createLimitRequest",
        "tags": [
          "Customers"
        ],
        "summary": "Ask for a higher limit for an existing customer",
        "description": "Asks OZAV to raise the limit of an approved customer - most often one\napproved at the entry limit. A customer that is not approved, or whose\naccount is closed or suspended, answers `409 CUSTOMER_NOT_ELIGIBLE`. Send the monthly amount you want\n(and, optionally, the per-transaction amount and a short reason).\n\nThe response lists the proofs of economic capacity that are still\nmissing for that amount in `documents_due`, each with the `doc_type`\nthat closes it in `POST /customers/{customer_id}/documents`. The same\nitems appear in `requirements.documents_due` of the customer, with\n`purpose: limit_increase`, and a `requirement.due` webhook follows within\nabout 15 minutes.\nProofs sent recently and not rejected are not asked again.\n\nThe request moves on its own: `awaiting_documents` while a proof is\nmissing, `in_review` once everything arrived, and `approved` (with\n`approved_limit`) when OZAV's compliance officer sets the new limit - it\ncan be lower than what you asked. `declined` carries no reason;\n`canceled` means the customer stopped being eligible. Subscribe to `limit_request.updated` to\nbe told of each change.\n\nOne open request per customer: a second one answers\n`409 LIMIT_REQUEST_ALREADY_OPEN`. Do not put personal data in `reason`.",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LimitRequestCreate"
              },
              "examples": {
                "monthly": {
                  "summary": "A higher monthly limit",
                  "value": {
                    "monthly_usd": "50000.00",
                    "per_transaction_usd": "10000.00",
                    "reason": "Monthly supplier payments are growing."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The limit request was recorded.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LimitRequest"
                },
                "examples": {
                  "awaiting": {
                    "summary": "Waiting for proofs of capacity",
                    "value": {
                      "id": "ozav_lir_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "limit_request",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "awaiting_documents",
                      "requested": {
                        "monthly_usd": "50000.00",
                        "per_transaction_usd": "10000.00"
                      },
                      "reason": "Monthly supplier payments are growing.",
                      "documents_due": [
                        {
                          "key": "bank_statement",
                          "doc_type": "bank_statement",
                          "label": "Bank statements for the last 3 months - for the requested limit of USD 50,000/month",
                          "status": "due"
                        },
                        {
                          "key": "tax_return",
                          "doc_type": "source_of_funds",
                          "label": "Personal income tax return (IRPF) with its filing receipt - for the requested limit of USD 50,000/month",
                          "status": "due"
                        }
                      ],
                      "approved_limit": null,
                      "created_at": "2026-09-30T12:00:00Z",
                      "updated_at": "2026-09-30T12:00:00Z",
                      "decided_at": null
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/IdempotencyProcessing"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/limit-requests \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"monthly_usd\": \"50000.00\",\n    \"per_transaction_usd\": \"10000.00\",\n    \"reason\": \"Monthly supplier payments are growing.\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/limit-requests\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"monthly_usd\": \"50000.00\",\n    \"per_transaction_usd\": \"10000.00\",\n    \"reason\": \"Monthly supplier payments are growing.\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/limit-requests\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"monthly_usd\": \"50000.00\",\n        \"per_transaction_usd\": \"10000.00\",\n        \"reason\": \"Monthly supplier payments are growing.\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listLimitRequests",
        "tags": [
          "Customers"
        ],
        "summary": "List a customer's limit requests",
        "description": "Lists the limit requests of a customer, newest first, using the\nstandard cursor pagination. Returns a `Page` of `LimitRequest`.",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Limit requests.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/LimitRequest"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One approved request",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_lir_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "limit_request",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "status": "approved",
                          "requested": {
                            "monthly_usd": "50000.00",
                            "per_transaction_usd": null
                          },
                          "reason": null,
                          "documents_due": [],
                          "approved_limit": {
                            "monthly_usd": "40000.00",
                            "per_transaction_usd": "10000.00"
                          },
                          "created_at": "2026-09-30T12:00:00Z",
                          "updated_at": "2026-10-02T15:00:00Z",
                          "decided_at": "2026-10-02T15:00:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/limit-requests \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/limit-requests\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/limit-requests\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/evidence": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "get": {
        "operationId": "getCustomerEvidencePack",
        "tags": [
          "Deprecated"
        ],
        "summary": "Retrieve the verification and decision record for a customer",
        "deprecated": true,
        "description": "**Deprecated.** In Money Movement a customer OZAV approved is approved,\nand one OZAV refused is refused: `GET /customers/{customer_id}` says\nwhich. This route stays available to the integrations that read it until\nthe date in OZAV's notice to them.\n\nReturns the **verification and decision record** for one customer: what\nOZAV verified, which lists were screened, which documents were assessed,\nwhich decisions were taken on their movements, and what happened\nafterwards. Hand it to your bank or your regulator as evidence that the\ndecision was made and can be defended.\n\nAsk for `format=html` to get a printable document instead of JSON; both\ncome from the same record, so they can never disagree.\n\n**An empty section means there is no record, never approval.** A section\nthat could not be read is listed in `secoesIndisponiveis` and the verdict\nfalls back to `em_analise` - the record never asserts what it did not\nread.\n\nThe record is assembled at request time and carries `geradoEm`. There is\nno frozen document id: a compliance document that freezes\nages silently, and an \"approved\" from three months ago shown to a bank\nwhile a new alert is open is worse than no document at all.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "evidence:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "`json` (default) or `html` for a printable document.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "html"
              ]
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Language of the printable document: `pt` (default) or `en`.",
            "schema": {
              "type": "string",
              "enum": [
                "pt",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The verification and decision record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvidencePack"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/evidence \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/evidence\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/evidence\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/customers/{customer_id}/credential": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CustomerId"
        }
      ],
      "get": {
        "operationId": "getCustomerCredential",
        "tags": [
          "Customers"
        ],
        "summary": "Issue a portable verification credential for an approved customer",
        "description": "Returns a **portable credential** signed by OZAV that attests what OZAV\nverified about this customer, when, and how - as an IETF SD-JWT\n(`vc+sd-jwt`, ES256). Hand it to the end user: they can present it to\nany third party and choose which attributes to disclose (for example\n`over_18` without `cpf`). Any verifier checks it offline against the\npublic key at `jwks_url` and confirms it is still in force at\n`status_url`.\n\n**Selectively disclosable claims:** `cpf`, `full_name`, `birth_date`,\n`over_18`, `kyc_level` (`3` = hosted session with document, liveness and\nface match; `2` = document-based identification), `method`,\n`verified_at`, `screening`. The JWT body itself carries no personal\ndata - only salted digests - so the credential leaks nothing the holder\ndoes not disclose.\n\nEach call issues a **new** credential (new `id`). Earlier ones stay in\nforce until they expire or the customer's status changes: a credential\nis reported `revoked` at `status_url` as soon as the customer is no\nlonger approved, is frozen, or is re-verified. There is no revocation\nstep for you to call. Issuance is capped per customer per day\n(`429 CREDENTIAL_ISSUANCE_LIMIT_REACHED` past the cap): keep the\ncredential you received rather than requesting one per use.\n\nIndividual customers identified by a Brazilian CPF only. Not issued in\nthe sandbox environment. Availability is enabled per organization by\nOZAV; until then the call returns `422 FEATURE_NOT_AVAILABLE`.\n",
        "x-ozav-entitlement": "customers",
        "x-ozav-scopes": [
          "customers:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "A freshly issued credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerCredential"
                },
                "examples": {
                  "issued": {
                    "summary": "A level-3 credential from a hosted session",
                    "value": {
                      "id": "ozav_vc_Q2xhdWRlRmFibGU1MQ",
                      "object": "credential",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "format": "vc+sd-jwt",
                      "credential": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZjK3NkLWp3dCIsImtpZCI6Im96YXYtMjAyNi0wOSJ9.eyJpc3MiOiJodHRwczovL2FwaS5vemF2LmlvIn0.MEUCIQ~WyJzYWx0Iiwib3Zlcl8xOCIsdHJ1ZV0~",
                      "kyc_level": 3,
                      "method": "hosted_session",
                      "issued_at": "2026-09-05T12:00:00Z",
                      "expires_at": "2027-09-05T12:00:00Z",
                      "jwks_url": "https://api.ozav.io/functions/v1/credencial-well-known/jwks.json",
                      "status_url": "https://api.ozav.io/functions/v1/credencial-well-known/credential-status/ozav_vc_Q2xhdWRlRmFibGU1MQ"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "`CREDENTIAL_SIGNING_UNAVAILABLE` - transient; retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/credential \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/credential\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/customers/ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1/credential\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/decisions": {
      "post": {
        "operationId": "createDecision",
        "tags": [
          "Decisions"
        ],
        "summary": "Get OZAV's verdict on a risk question",
        "description": "Runs OZAV's own engines on the input and returns the verdict with its reasons and checks.\nThe verdict is recorded (your input is not) so you can read it back and report its outcome.\n\nMissing or stale data is `REVIEW`, never `ALLOW`. If an engine cannot answer at all, the\ncall returns `503 DECISION_UNAVAILABLE`, nothing is recorded and nothing is charged.\n\nSend an `Idempotency-Key` to retry safely: the same key and body return the same decision\nand are not charged again; the same key with a different body answers `409`.\n\nRequires the `decisions:write` scope. API keys created before this scope existed do not\ngain it: create a new key with the scopes you need.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "decisions:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/X402PaymentSignature"
          },
          {
            "$ref": "#/components/parameters/XOzavSimulatePayment"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionCreateRequest"
              },
              "examples": {
                "name_match": {
                  "summary": "Is this person on a sanctions list?",
                  "value": {
                    "type": "name.match",
                    "input": {
                      "name": "Maria Oliveira",
                      "date_of_birth": "1985-04-12"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decision"
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/IdempotencyProcessing"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/decisions \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"type\": \"name.match\",\n    \"input\": {\n      \"name\": \"Maria Oliveira\",\n      \"date_of_birth\": \"1985-04-12\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/decisions\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"type\": \"name.match\",\n    \"input\": {\n      \"name\": \"Maria Oliveira\",\n      \"date_of_birth\": \"1985-04-12\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/decisions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"type\": \"name.match\",\n        \"input\": {\n            \"name\": \"Maria Oliveira\",\n            \"date_of_birth\": \"1985-04-12\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/decisions/{decision_id}": {
      "get": {
        "operationId": "getDecision",
        "tags": [
          "Decisions"
        ],
        "summary": "Retrieve a decision",
        "description": "Returns a decision your organization requested, with its outcome if you reported one.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "decisions:read"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "decision_id",
            "in": "path",
            "required": true,
            "description": "The decision id (`ozav_dec_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The decision.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decision"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/decisions/ozav_dec_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/decisions/ozav_dec_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/decisions/ozav_dec_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/decisions/{decision_id}/outcome": {
      "post": {
        "operationId": "recordDecisionOutcome",
        "tags": [
          "Decisions"
        ],
        "summary": "Report what happened after a decision",
        "description": "Tells OZAV how the case ended. An outcome is recorded once: a second report returns\n`409 OUTCOME_ALREADY_RECORDED`. Outcomes help OZAV measure and improve its rules.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "decisions:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "decision_id",
            "in": "path",
            "required": true,
            "description": "The decision id (`ozav_dec_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionOutcomeRequest"
              },
              "examples": {
                "false_positive": {
                  "summary": "The name was a namesake",
                  "value": {
                    "outcome": "false_positive",
                    "note": "Different date of birth confirmed by document."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decision with its outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Decision"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/decisions/ozav_dec_01J8Z9K2M3N4P5Q6R7S8T9U0V1/outcome \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"outcome\": \"false_positive\",\n    \"note\": \"Different date of birth confirmed by document.\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/decisions/ozav_dec_01J8Z9K2M3N4P5Q6R7S8T9U0V1/outcome\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"outcome\": \"false_positive\",\n    \"note\": \"Different date of birth confirmed by document.\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/decisions/ozav_dec_01J8Z9K2M3N4P5Q6R7S8T9U0V1/outcome\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"outcome\": \"false_positive\",\n        \"note\": \"Different date of birth confirmed by document.\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-platforms": {
      "post": {
        "operationId": "registerAgentPlatform",
        "tags": [
          "Agents"
        ],
        "summary": "Register your organization as an agent platform",
        "description": "Registers the calling organization as a platform that runs AI agents for other\ncompanies. One platform per organization and environment: calling again returns the same one.\nShare the returned `id` with the companies that will authorize your agents.\n\nTo run agents with your own organization's money, register with `first_party: true`. The platform then\nreceives grants only from your organization, and an administrator still creates each grant in the\nportal with the authenticator code: a company's own agents get the same approval as anyone's.\nCalling again with the other mode answers `409 AGENT_PLATFORM_MODE_CONFLICT`.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentPlatformRequest"
              },
              "examples": {
                "platform": {
                  "summary": "Register a platform",
                  "value": {
                    "name": "Acme Agents"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The platform, already registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentPlatform"
                }
              }
            }
          },
          "201": {
            "description": "The platform, registered now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentPlatform"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-platforms \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Acme Agents\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-platforms\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"name\": \"Acme Agents\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agent-platforms\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"name\": \"Acme Agents\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-grants": {
      "get": {
        "operationId": "listAgentGrants",
        "tags": [
          "Agents"
        ],
        "summary": "List agent grants",
        "description": "The grants your organization gave (as the company that owns the money) and the\ngrants it received (as a platform). A company creates a grant in the OZAV portal, with\nmulti-factor authentication, or with `POST /agent-grants`.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "responses": {
          "200": {
            "description": "The grants.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentGrantList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-grants \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-grants\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agent-grants\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "post": {
        "operationId": "createAgentGrant",
        "tags": [
          "Agents"
        ],
        "summary": "Grant an agent mandate, for your organization or one of its customers",
        "description": "Your organization gives a platform's agents a mandate to move money: its own money, or the money of one of\nyour customers (`customer_id`, a person or a business you onboarded). The grant is active at once. Calling\nthis endpoint with your admin key is the approval: the customer does not approve it separately, so only\ngrant for a customer whose instruction you hold.\n\nOnly a key holding `agent_grants:write` can call it. The `operator` key role never receives that scope;\nan `admin` key does, and an existing key gains it only if you mint a new one with it.\n\nThe same rules as a grant made in the portal apply:\n\n- the platform (`platform_id`) must be active and in the same environment as your key;\n- a third-party platform never receives a grant from its own organization (`422 SELF_AUTHORIZATION`),\n  and a first-party platform only from it (`422 FIRST_PARTY_PLATFORM`);\n- the mandate is validated like everywhere else (`422 MANDATE_INVALID`).\n\nAn agent under a customer's grant moves only that customer's money: its payouts leave the customer's\nwallets, go to the customer's payout destinations, and its fundings land in the customer's wallets.\n\nThe same `idempotency_key` with the same body returns the same grant (`200`); with a different body it\nanswers `409 IDEMPOTENCY_KEY_REUSED`.\n\n- `404 CUSTOMER_NOT_FOUND`: no such customer in your organization and environment.\n- `404 AGENT_PLATFORM_NOT_FOUND`: no active platform with this id in your environment.\n- `422 CUSTOMER_DEACTIVATED`: you deactivated this customer.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_grants:write"
        ],
        "x-ozav-availability": "beta",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentGrantRequest"
              },
              "examples": {
                "for_a_customer": {
                  "summary": "A monthly payout and funding mandate for one of your customers",
                  "value": {
                    "idempotency_key": "grant-cus-42",
                    "platform_id": "ozav_apl_01J9Z8Y7X6W5V4T3S2R1Q0P9N8",
                    "customer_id": "ozav_cus_01J9Z8Y7X6W5V4T3S2R1Q0P9N8",
                    "mandate": {
                      "actions": [
                        "fiat_payout",
                        "fiat_funding"
                      ],
                      "per_payment_limit_usd": 50,
                      "recurring_budget": {
                        "period": "month",
                        "amount_usd": 200,
                        "timezone": "America/Sao_Paulo"
                      },
                      "funding_limits": {
                        "per_request_usd": 500,
                        "daily_usd": 800
                      },
                      "valid_until": "2099-12-31T00:00:00Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The grant, already created with this idempotency key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentGrant"
                }
              }
            }
          },
          "201": {
            "description": "The grant, created and active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentGrant"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-grants \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"grant-cus-42\",\n    \"platform_id\": \"ozav_apl_01J9Z8Y7X6W5V4T3S2R1Q0P9N8\",\n    \"customer_id\": \"ozav_cus_01J9Z8Y7X6W5V4T3S2R1Q0P9N8\",\n    \"mandate\": {\n      \"actions\": [\n        \"fiat_payout\",\n        \"fiat_funding\"\n      ],\n      \"per_payment_limit_usd\": 50,\n      \"recurring_budget\": {\n        \"period\": \"month\",\n        \"amount_usd\": 200,\n        \"timezone\": \"America/Sao_Paulo\"\n      },\n      \"funding_limits\": {\n        \"per_request_usd\": 500,\n        \"daily_usd\": 800\n      },\n      \"valid_until\": \"2099-12-31T00:00:00Z\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-grants\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"grant-cus-42\",\n    \"platform_id\": \"ozav_apl_01J9Z8Y7X6W5V4T3S2R1Q0P9N8\",\n    \"customer_id\": \"ozav_cus_01J9Z8Y7X6W5V4T3S2R1Q0P9N8\",\n    \"mandate\": {\n      \"actions\": [\n        \"fiat_payout\",\n        \"fiat_funding\"\n      ],\n      \"per_payment_limit_usd\": 50,\n      \"recurring_budget\": {\n        \"period\": \"month\",\n        \"amount_usd\": 200,\n        \"timezone\": \"America/Sao_Paulo\"\n      },\n      \"funding_limits\": {\n        \"per_request_usd\": 500,\n        \"daily_usd\": 800\n      },\n      \"valid_until\": \"2099-12-31T00:00:00Z\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agent-grants\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"grant-cus-42\",\n        \"platform_id\": \"ozav_apl_01J9Z8Y7X6W5V4T3S2R1Q0P9N8\",\n        \"customer_id\": \"ozav_cus_01J9Z8Y7X6W5V4T3S2R1Q0P9N8\",\n        \"mandate\": {\n            \"actions\": [\n                \"fiat_payout\",\n                \"fiat_funding\",\n            ],\n            \"per_payment_limit_usd\": 50,\n            \"recurring_budget\": {\n                \"period\": \"month\",\n                \"amount_usd\": 200,\n                \"timezone\": \"America/Sao_Paulo\",\n            },\n            \"funding_limits\": {\n                \"per_request_usd\": 500,\n                \"daily_usd\": 800,\n            },\n            \"valid_until\": \"2099-12-31T00:00:00Z\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-grants/{grant_id}": {
      "get": {
        "operationId": "getAgentGrant",
        "tags": [
          "Agents"
        ],
        "summary": "Retrieve an agent grant",
        "description": "A grant and the mandate in force. Readable by the company that gave it and by the\nplatform that received it.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "grant_id",
            "in": "path",
            "required": true,
            "description": "The grant id (`ozav_agr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentGrant"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-grants/{grant_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-grants/{grant_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agent-grants/{grant_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-grants/{grant_id}/prepaid-resources": {
      "post": {
        "operationId": "registerAgentPrepaidResource",
        "tags": [
          "Agents"
        ],
        "summary": "Register credit the company bought outside OZAV, for its agents to consume",
        "description": "Credit the company already paid a seller for, outside OZAV (API credits, a prepaid plan), registered under a grant so\nits agents can declare what they consume. Nothing moves money: the resource is a balance in whole units of whatever the\nseller sells (`tokens`, `requests`, `credits`), and only goes down. Buying more is a new resource.\n\nOnly the account holder registers resources, with its administrator key (`agent_grants:write`). Leave `agent_id` out\nto let every agent of the grant consume it, or name one agent. The same `idempotency_key` with the same body returns\nthe same resource (`200`).\n\n- `409 PREPAID_RESOURCE_CONFLICT`: this `idempotency_key` was used for a different resource.\n- `409 AGENT_GRANT_NOT_ACTIVE`: the grant is revoked or suspended.\n- `422 PREPAID_RESOURCE_ALREADY_EXPIRED`: `expires_at` is not in the future.\n- `403 PREPAID_RESOURCE_HOLDER_ONLY`: the key lacks `agent_grants:write`.\n- `404 AGENT_GRANT_NOT_FOUND` / `404 AGENT_NOT_FOUND`: not your grant, or not an agent of it.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_grants:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "grant_id",
            "in": "path",
            "required": true,
            "description": "The grant id (`ozav_agr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentPrepaidResourceRequest"
              },
              "examples": {
                "credits": {
                  "summary": "Two million tokens bought from a model seller",
                  "value": {
                    "idempotency_key": "tokens-october",
                    "seller_domain": "api.openai.com",
                    "unit": "tokens",
                    "units": "2000000",
                    "description": "October token plan"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resource registered earlier under this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentPrepaidResource"
                }
              }
            }
          },
          "201": {
            "description": "The resource was registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentPrepaidResource"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-grants/{grant_id}/prepaid-resources \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"tokens-october\",\n    \"seller_domain\": \"api.openai.com\",\n    \"unit\": \"tokens\",\n    \"units\": \"2000000\",\n    \"description\": \"October token plan\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-grants/{grant_id}/prepaid-resources\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"tokens-october\",\n    \"seller_domain\": \"api.openai.com\",\n    \"unit\": \"tokens\",\n    \"units\": \"2000000\",\n    \"description\": \"October token plan\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agent-grants/{grant_id}/prepaid-resources\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"tokens-october\",\n        \"seller_domain\": \"api.openai.com\",\n        \"unit\": \"tokens\",\n        \"units\": \"2000000\",\n        \"description\": \"October token plan\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listAgentPrepaidResources",
        "tags": [
          "Agents"
        ],
        "summary": "List a grant's prepaid resources and what is left of each",
        "description": "Every prepaid resource of the grant, newest first, with `balance_units` as it is now. The account holder and the\ngrant's platform can read them; anyone else gets `404 AGENT_GRANT_NOT_FOUND`.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "grant_id",
            "in": "path",
            "required": true,
            "description": "The grant id (`ozav_agr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grant's prepaid resources.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list",
                      "description": "Always the literal `list`."
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentPrepaidResource"
                      },
                      "description": "The resources, newest first."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-grants/{grant_id}/prepaid-resources \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-grants/{grant_id}/prepaid-resources\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agent-grants/{grant_id}/prepaid-resources\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-grants/{grant_id}/revoke": {
      "post": {
        "operationId": "revokeAgentGrant",
        "tags": [
          "Agents"
        ],
        "summary": "Revoke an agent grant",
        "description": "Either side can revoke: the company withdraws the authorization, or the platform\ngives it up. Revoking takes effect immediately: a payment already being recorded is refused by\nthe database check. Revoking is final - a revoked grant never becomes active again. The response\nkeeps the mandate that was granted, as `GET` does. Revoking again returns the revoked grant.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "grant_id",
            "in": "path",
            "required": true,
            "description": "The grant id (`ozav_agr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The revoked grant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentGrant"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-grants/{grant_id}/revoke \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-grants/{grant_id}/revoke\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agent-grants/{grant_id}/revoke\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents": {
      "post": {
        "operationId": "registerAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Register an agent under a grant",
        "description": "Only the platform the grant was given to can register agents under it, and only\nwhile the grant is active. An agent may carry its own narrower mandate. A child agent\n(`parent_agent`) can never be wider than its parent or the grant: a wider `own_mandate` is\nrefused with `422 MANDATE_WIDER_THAN_PARENT`, naming the fields as you wrote them\n(`per_payment_limit_usd`, `allowed_networks`, ...). A suspended platform gets `403\nAGENT_PLATFORM_SUSPENDED`.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentRegisterRequest"
              },
              "examples": {
                "agent": {
                  "summary": "Register a purchasing agent",
                  "value": {
                    "grant": "ozav_agr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "label": "Data purchasing agent",
                    "platform_agent_ref": "agent-7f3a"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The agent's passport.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"grant\": \"ozav_agr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"label\": \"Data purchasing agent\",\n    \"platform_agent_ref\": \"agent-7f3a\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"grant\": \"ozav_agr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"label\": \"Data purchasing agent\",\n    \"platform_agent_ref\": \"agent-7f3a\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"grant\": \"ozav_agr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"label\": \"Data purchasing agent\",\n        \"platform_agent_ref\": \"agent-7f3a\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}": {
      "get": {
        "operationId": "getAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Retrieve an agent's passport",
        "description": "Who the agent is, which grant it acts under, the mandate in force, and whether it\ncan transact now (`can_transact`). Readable by the platform and by the company.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The passport.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agents/{agent_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/revoke": {
      "post": {
        "operationId": "revokeAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Revoke an agent",
        "description": "The platform or the company revokes one agent. Its next payment request is refused\nand recorded. Revoking is final, and it reaches the agent's children (and theirs): every agent\nregistered under it is revoked too. Revoking again returns the revoked agent.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The revoked agent's passport.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/revoke \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/revoke\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/revoke\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/suspend": {
      "post": {
        "operationId": "suspendAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Suspend an agent",
        "description": "Pause an agent without revoking it. A suspended agent cannot pay, pay out, request a funding, register a payee or get a passport,\nand neither can any agent registered under it: every request re-reads the agent's whole lineage. The children keep their own status,\nso resuming the parent does not wake a child that was suspended on its own.\n\nSuspending is a brake, so it is easy: the platform the agent belongs to may suspend it, and so may the company that granted the\nmandate, with a key that has `agent_grants:write`. Suspending a suspended agent returns it unchanged.\n\n- `403 AGENT_SUSPEND_NOT_ALLOWED`: the company called with a key without `agent_grants:write`.\n- `409 AGENT_REVOKED`: a revoked agent is final; it cannot be suspended or resumed.\n- `404 AGENT_NOT_FOUND`: no such agent for this organization.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The suspended agent's passport.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/suspend \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/suspend\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/suspend\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/resume": {
      "post": {
        "operationId": "resumeAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Resume a suspended agent",
        "description": "Give a suspended agent its power back. Only the company that granted the mandate can resume an agent, with a key that has\n`agent_grants:write`; the platform can suspend its agent but never resume it. Resuming an active agent returns it unchanged.\nAn agent under a parent that is still suspended stays unable to act until the parent is resumed too.\n\n- `403 AGENT_RESUME_HOLDER_ONLY`: the caller is not the company that granted the mandate, or its key lacks `agent_grants:write`.\n- `409 AGENT_REVOKED`: a revoked agent is final.\n- `404 AGENT_NOT_FOUND`: no such agent for this organization.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The resumed agent's passport.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/resume \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/resume\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/resume\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/passport": {
      "post": {
        "operationId": "issueAgentPassport",
        "tags": [
          "Agents"
        ],
        "summary": "Issue the agent's signed passport",
        "description": "OZAV signs a passport for the agent: an SD-JWT (`vc+sd-jwt`, `vct`\n`ozav:agent-passport:v1`) that says who the agent is, which company it acts for, through which\nplatform, and under which mandate (version, hash and limits). The platform or the company can\nrequest it.\n\nIn the sandbox the passport is a test passport, so you can integrate issuing, presenting and verifying\nbefore going live. It is signed with a separate sandbox key, its issuer ends in `/sandbox`, its `vct`\nis `ozav:agent-passport:sandbox:v1`, `env` is `test` and the response says `sandbox: true`. Its\n`jwks_url` is the sandbox key set, and `POST .../credentials/verify-sandbox` verifies it. A production\nverifier never accepts it: the key, the issuer and the `vct` are all different. When OZAV has no\nsandbox key configured, the sandbox answers `503 AGENT_PASSPORT_UNAVAILABLE`.\n\nVisible in the token: the issuer, the agent id (`sub`), the environment (`env`), the validity\nand where to check the status. Selectively disclosed: `principal`,\n`platform`, `grant`, `mandate`, `agent_mandate` and `agent`. The agent chooses what to show.\n\nAnyone can verify it offline against `jwks_url`, and check the status at `status_url` without\nan API key. The status is computed when it is read. A passport stops being valid when the agent\nor the grant is revoked or suspended, when an agent above it is suspended or revoked, when the grant moves to a new mandate version, when the\nmandate expires, or when the company is no longer eligible (its organization, its verification\nor its account; the public reason is the same `principal_not_eligible` for all of them). It\nlasts at most 7 days, and never past the mandate's end.\n\n`409` when the agent cannot carry a passport right now: `AGENT_NOT_ACTIVE`,\n`AGENT_GRANT_NOT_ACTIVE`, `AGENT_MANDATE_NOT_IN_FORCE` or `PRINCIPAL_NOT_ELIGIBLE`. `503`\n`AGENT_PASSPORT_UNAVAILABLE` when OZAV cannot sign right now. `403 AGENT_PLATFORM_SUSPENDED`\nwhen the requesting platform is suspended.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The signed passport.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentPassport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/passport \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/passport\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/passport\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/request-signatures": {
      "post": {
        "operationId": "signAgentRequest",
        "tags": [
          "Agents"
        ],
        "summary": "Sign an HTTP request the agent is about to make",
        "description": "**Beta.** OZAV signs the HTTP request your agent is about to send to a site (the IETF Web\nBot Auth protocol, over RFC 9421 HTTP Message Signatures). Send the\ntarget `authority` (and, to bind the signature tighter, the `method` and `path`); OZAV returns\nthe headers to add to that request: `Signature-Agent`, `Signature-Input`, `Signature` and\n`OZAV-Agent` (the agent id, covered by the signature).\n\nThe site verifies the signature with OZAV's public key, from the directory at\n`directory_url` (`https://ozav.io/.well-known/http-message-signatures-directory`), with no\nOZAV account. The signature covers `@authority`, the `method` and `path` when you send them,\n`Signature-Agent` and `OZAV-Agent`; it is valid for 60 seconds, with a fresh nonce each time.\nThe agent never holds a key: OZAV signs only while the agent (and every agent above it), its\ngrant, its mandate and its company are in force, so revoking the agent or the grant takes\neffect on the next request.\n\nOnly the platform the agent belongs to can request signatures. `409` when OZAV does not sign\nfor the agent right now: `AGENT_NOT_ACTIVE`, `AGENT_GRANT_NOT_ACTIVE`,\n`AGENT_MANDATE_NOT_IN_FORCE` or `PRINCIPAL_NOT_ELIGIBLE`. `422\nREQUEST_SIGNATURE_TARGET_INVALID` when the authority, method or path is malformed. `503\nAGENT_WEB_IDENTITY_UNAVAILABLE` while signing is not enabled. In the sandbox the headers have\nthe same form, `simulated` is `true`, and they are signed with a key no directory lists, so no\nsite accepts them.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentRequestSignatureRequest"
              },
              "examples": {
                "get": {
                  "summary": "Sign a GET to a data API",
                  "value": {
                    "authority": "api.example.com",
                    "method": "GET",
                    "path": "/v1/quotes"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The headers to add to the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentRequestSignature"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/request-signatures \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"authority\": \"api.example.com\",\n    \"method\": \"GET\",\n    \"path\": \"/v1/quotes\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/request-signatures\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"authority\": \"api.example.com\",\n    \"method\": \"GET\",\n    \"path\": \"/v1/quotes\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/request-signatures\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"authority\": \"api.example.com\",\n        \"method\": \"GET\",\n        \"path\": \"/v1/quotes\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/payouts": {
      "post": {
        "operationId": "requestAgentPayout",
        "tags": [
          "Agents"
        ],
        "summary": "Pay a registered beneficiary in fiat currency",
        "description": "The agent pays a registered payout destination of the company (for example a\nBrazilian supplier by Pix, or a US bank account in dollars) from the company's managed stablecoin wallet. The\ncurrency the beneficiary receives is `amount.currency` and must match the destination's currency. OZAV quotes the\npayout, checks the grant's mandate and the agent's own against the quoted dollar amount, the corridor\n(`USDC>BRL`, `USDC>USD`, ...) and the allowed payees, and records the request. In the sandbox the same path runs\nwith simulated money: fixed rates per US dollar of stablecoin (R$ 5.00, US$ 1.00, EUR 0.90; any other currency\nanswers `422 AGENT_PAYOUT_QUOTE_UNAVAILABLE` with `UNSUPPORTED_CURRENCY`), a simulated transfer (the same id for the\nsame action) and no call to any external service; the receipt in `payout` carries `simulated: true`.\n\nWithin the mandate, OZAV creates the payout through the same transfer flow the company uses,\nwith the same compliance checks, beneficiary ownership rules and ledger reservation. The\nresponse carries the transfer in `payout`.\n\nThe stablecoin can also leave the agent's own wallet, the one OZAV keeps for it (`POST /agents/{agent_id}/wallet`):\nsend `source: { type: \"agent_wallet\", currency: \"USDC\", chain: \"polygon\" }`. Production only, Polygon only, and\nonly while the feature is enabled for your platform. OZAV first moves the exact amount from the agent's wallet\nto its own treasury (the agent's wallet signs inside OZAV's own infrastructure, and the per-payment limit sealed\ninto that wallet bounds the amount), then creates the transfer. If anything is uncertain after that first move,\nnothing is released: the request answers `503 AGENT_PAYOUT_OUTCOME_UNKNOWN`, the budget stays held and OZAV\nreviews it.\n- `422 AGENT_WALLET_NOT_ACTIVE`: the agent has no active wallet on that network in OZAV's infrastructure (or\n  the request is for the sandbox). Nothing was quoted.\n- `422 AGENT_PAYOUT_ABOVE_WALLET_LIMIT`: the amount is above the per-payment limit sealed in the agent's\n  wallet. Nothing was paid; the budget was released.\n- `503 AGENT_ENCLAVE_PAYOUTS_NOT_ENABLED`: payouts from the agent's wallet are not enabled yet. Nothing was quoted.\n\n- Above the approval threshold: `202`. Nothing is paid until a person at the company approves\n  it in the OZAV portal. The decision arrives as the webhook `agent_action.updated` (`authorized`,\n  `refused` or `expired`). Once a person approves, OZAV runs the payout on its own, with no new\n  request from you: the webhook then reports it `executed`. If it could not run (for example the\n  new quote costs more than allowed), it stays `authorized` and you can repeat the request with\n  the same `idempotency_key` and the same\n  body: the destination, the amount and currency the beneficiary receives, the wallet and the\n  network are what was approved, and a repeat that changes any of them is\n  `409 AGENT_PAYMENT_CONFLICT`. Either way OZAV quotes again, and pays only if the new quote costs at most\n  2% more than the approved amount (`409 AGENT_PAYOUT_DIFFERS_FROM_APPROVED` otherwise). A quote\n  whose target is not the amount and currency you asked for is never paid: it answers\n  `422 AGENT_PAYOUT_QUOTE_UNAVAILABLE`.\n- `503 AGENT_PAYOUT_OUTCOME_UNKNOWN`: OZAV could not confirm whether the payout was created.\n  Repeat with the same `idempotency_key`, never a new one. OZAV marks the payout as dispatched, in\n  one database transaction that also checks the grant, the mandate and the agent's lineage, before\n  it calls the transfer service. Once dispatched, the repeat completes it even if the grant was\n  revoked afterwards (the transfer may exist). Before that, a grant, an agent or a parent revoked\n  or suspended, or a mandate that changed, makes the repeat `422 AGENT_PAYMENT_OUTSIDE_MANDATE` (or\n  `409 AGENT_MANDATE_CHANGED`), and nothing is paid. The retry sends the same payout\n  again and returns the original result; it never pays twice.\n- `422 AGENT_PAYOUT_REFUSED`: refused before any money moved. `details.upstream.code` names a\n  problem you can fix (amount, currency, corridor, destination, quote, balance, or the balance on\n  that network: `CHAIN_COVERAGE_INSUFFICIENT` means another network may cover it); any other\n  reason reads `REFUSED`. The budget is released.\n- `422 AGENT_PAYOUT_QUOTE_UNAVAILABLE`: no quote for this corridor and amount right now\n  (`details.upstream.code`, same rule).\n- `422 AGENT_PAYMENT_NOT_PERMITTED`: this payout cannot be made from this account right now.\n  The message is fixed; nothing was paid.\n- `422 WALLET_QUORUM_REQUIRED`: the company's wallet policy requires several of its keys to\n  approve this amount; an agent payout cannot wait for that.\n- `422 AGENT_PAYMENT_OUTSIDE_MANDATE`: outside the mandate, with the reasons (among them `above_recurring_budget`\n  when the amount would pass the mandate's recurring budget for the current window, and `above_agent_allocation` when\n  the agent has an allocation and the amount does not fit in what is left of it in the source currency). The refusal is\n  recorded. The source asset and network are checked against `allowed_assets` and\n  `allowed_networks` too. It is also returned, without reasons, when the grant, the agent or an\n  agent above it is revoked or suspended, or the mandate expired, between the quote and the\n  transfer; nothing was paid.\n- `409 AGENT_MANDATE_CHANGED`: the mandate changed after the payout was approved. Nothing was\n  paid; request it again.\n- `409 AGENT_PAYMENT_CONFLICT`: this `idempotency_key` was used under this grant for a different\n  payout, or by a different agent, or the repeat changes the destination, the amount or currency\n  the beneficiary receives, the wallet or the network.\n- `409 AGENT_PAYMENT_FAILED`: a repeat of a payout that failed. Use a new key to try again.\n- `409 AGENT_GRANT_NOT_ACTIVE` / `409 AGENT_NOT_ACTIVE`: checked before quoting, so a revoked\n  grant never spends the company's request limit.\n- `403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n- `503 AGENT_PAYOUTS_NOT_ENABLED`: agent payouts are not enabled yet in production; nothing was quoted. The\n  sandbox does not need it.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentPayoutRequest"
              },
              "examples": {
                "pix": {
                  "summary": "Pay a supplier R$ 1.250,00 by Pix, from USDC on Polygon",
                  "value": {
                    "idempotency_key": "invoice-2026-0917",
                    "payout_destination_id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "amount": {
                      "amount": "1250.00",
                      "currency": "BRL"
                    },
                    "source": {
                      "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2",
                      "currency": "USDC",
                      "chain": "polygon"
                    }
                  }
                },
                "agent_wallet": {
                  "summary": "Pay a supplier R$ 1.250,00 by Pix from the agent's own wallet",
                  "value": {
                    "idempotency_key": "invoice-2026-0918",
                    "payout_destination_id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "amount": {
                      "amount": "1250.00",
                      "currency": "BRL"
                    },
                    "source": {
                      "type": "agent_wallet",
                      "currency": "USDC",
                      "chain": "polygon"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recorded payout request (a retry of one already created).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "201": {
            "description": "The payout was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "202": {
            "description": "Recorded and waiting for a person at the company to approve.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/payouts \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"invoice-2026-0917\",\n    \"payout_destination_id\": \"ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"amount\": {\n      \"amount\": \"1250.00\",\n      \"currency\": \"BRL\"\n    },\n    \"source\": {\n      \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2\",\n      \"currency\": \"USDC\",\n      \"chain\": \"polygon\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/payouts\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"invoice-2026-0917\",\n    \"payout_destination_id\": \"ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"amount\": {\n      \"amount\": \"1250.00\",\n      \"currency\": \"BRL\"\n    },\n    \"source\": {\n      \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2\",\n      \"currency\": \"USDC\",\n      \"chain\": \"polygon\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/payouts\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"invoice-2026-0917\",\n        \"payout_destination_id\": \"ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"amount\": {\n            \"amount\": \"1250.00\",\n            \"currency\": \"BRL\",\n        },\n        \"source\": {\n            \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2\",\n            \"currency\": \"USDC\",\n            \"chain\": \"polygon\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/yield-applications": {
      "post": {
        "operationId": "requestAgentYieldApplication",
        "tags": [
          "Agents"
        ],
        "summary": "Apply the company's custodied balance to managed yield",
        "description": "The agent applies part of the company's custodied stablecoin balance to a managed yield strategy\n(`GET /yield/strategies`). It is the same managed path as `POST /yield/applications`: OZAV records an order and\napplies it under its custody quorum, with the same exposure caps and compliance checks. The money stays the\ncompany's: it becomes a yield position in its name. The agent's own wallet is not used.\n\nThe mandate decides: the grant's mandate and the agent's own must have the `yield_application` action and\n`yield_rules` naming the strategy; the amount must fit `yield_rules.per_application_usd` and\n`yield_rules.daily_usd`. An application is not spending, so the spending limits (`per_payment_limit_usd`,\n`daily_limit_usd`, `total_budget_usd`, `recurring_budget`) do not apply. At or above\n`human_approval_above_usd` the response is `202` and nothing is applied until a person at the company approves\nit in the OZAV portal; then repeat the request with the same `idempotency_key` and body.\n\nThe agent never gets more than the company could do with its own API key. Before anything is recorded, OZAV\nchecks that the company has managed custody, a live API key with the `yield:apply` scope, and managed yield\nturned on for its account; otherwise the request is `422 AGENT_YIELD_APPLICATION_REFUSED` with\n`details.upstream.code`.\n\nThe response is `executed` once the order exists (`yield_application.application_id`); the order then runs on\nits own. The action becomes `settled` when the order is applied on chain, or `returned` when it is refused or\nfails before money moves (the daily limit is released). Follow it with\n`GET /agents/{agent_id}/yield-applications/{action_id}` or the `agent_action.updated` webhook. In the sandbox the\ndecision runs for real and the order is simulated; `POST /agent-actions/{action_id}/simulate` settles or returns it.\n\nThere is no agent redemption yet: taking money out of a managed yield position is not available through the\nAPI.\n\n- `503 AGENT_YIELD_NOT_ENABLED`: agent yield applications are not enabled yet (live and sandbox). Nothing was\n  recorded.\n- `422 AGENT_YIELD_APPLICATION_REFUSED`: the company cannot apply to yield right now. `details.upstream.code` is\n  `CUSTODY_NOT_ENABLED`, `YIELD_APPLY_NOT_GRANTED`, `MANAGED_YIELD_NOT_ENABLED` or\n  `MANAGED_YIELD_CLIENT_NOT_ENABLED` (nothing was recorded), or `REFUSED` when the order was refused before it\n  existed (the action is `failed`).\n- `404 YIELD_STRATEGY_NOT_FOUND`: no such strategy. `422 AGENT_YIELD_CURRENCY_MISMATCH`: `amount.currency` is\n  not the strategy's asset, or has more decimal places than it.\n- `409 AGENT_YIELD_APPLICATION_IN_FLIGHT`: another application to the same strategy is still running for this\n  company. Nothing was applied; use a new `idempotency_key` once it finishes.\n- `422 AGENT_PAYMENT_OUTSIDE_MANDATE`: outside the mandate, with the reasons (`yield_strategy_not_allowed`,\n  `above_yield_per_application`, `above_yield_daily`, `yield_rules_missing`, `action_not_allowed`, ...). The\n  refusal is recorded.\n- `409 AGENT_PAYMENT_CONFLICT`: this `idempotency_key` was used for a different request.\n- `409 AGENT_PAYMENT_FAILED`: a repeat of an application that failed, or was refused or failed before money moved. Use a new\n  key to try again.\n- `409 AGENT_GRANT_NOT_ACTIVE` / `409 AGENT_NOT_ACTIVE` / `409 AGENT_MANDATE_CHANGED`. A grant, an agent or a parent revoked\n  or suspended after the approval, or after a request that did not finish, refuses the repeat\n  (`422 AGENT_PAYMENT_OUTSIDE_MANDATE`) and no order is created.\n- `403 NOT_THE_GRANTED_PLATFORM`: only the platform this agent belongs to can apply to yield for it.\n- `403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentYieldApplicationRequest"
              },
              "examples": {
                "usdc": {
                  "summary": "Apply US$ 2,500 of USDC to a strategy on Base",
                  "value": {
                    "idempotency_key": "treasury-sweep-2026-10-16",
                    "strategy_id": "base-usdc-prime",
                    "amount": {
                      "amount": "2500.00",
                      "currency": "USDC"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recorded application (a retry of one already created).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "201": {
            "description": "The order was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "202": {
            "description": "Recorded and waiting for a person at the company to approve.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/yield-applications \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"treasury-sweep-2026-10-16\",\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": {\n      \"amount\": \"2500.00\",\n      \"currency\": \"USDC\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/yield-applications\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"treasury-sweep-2026-10-16\",\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": {\n      \"amount\": \"2500.00\",\n      \"currency\": \"USDC\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/yield-applications\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"treasury-sweep-2026-10-16\",\n        \"strategy_id\": \"base-usdc-prime\",\n        \"amount\": {\n            \"amount\": \"2500.00\",\n            \"currency\": \"USDC\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/yield-applications/{action_id}": {
      "get": {
        "operationId": "getAgentYieldApplication",
        "tags": [
          "Agents"
        ],
        "summary": "Read an agent's yield application and its order",
        "description": "The yield application (the company or the platform reads it), with the managed yield order as it is now\n(`pending`, `running`, `applied`, `refused`, `failed`, `indeterminate`). The action itself closes from the\norder on OZAV's schedule; this read shows the order in between. The order's `detail` and `tx_hash` are the\ncompany's: the platform reads them as `null`.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "action_id",
            "in": "path",
            "required": true,
            "description": "The action id (`ozav_aga_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The application and its order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentYieldApplication"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/yield-applications/{action_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/yield-applications/{action_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/yield-applications/{action_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/funding-from-wallet": {
      "post": {
        "operationId": "fundAgentAllocation",
        "tags": [
          "Agents"
        ],
        "summary": "Set aside part of the company's balance for one agent",
        "description": "Raises the agent's allocation: the part of the account holder's balance this agent may spend, per currency. Once an\nagent has an allocation, its payments and payouts must fit in what is left of it, on top of every mandate limit, and an\nasset with no allocation cannot be spent at all. An agent with no allocation is not limited this way.\n\n**No money moves.** The allocation is a limit kept by OZAV inside the account holder's balance, not a separate account:\nthe holder can still spend the same balance by other paths. At the moment of the request, every unspent allocation of\nthe holder in that currency plus this amount must fit in the holder's available balance. A spend that is returned,\nexpires or fails gives its amount back to the allocation by itself.\n\nCalled by the account holder or by the agent's platform. The same `idempotency_key` with the same body returns the same\nresult (`200`). In the sandbox the holder balance is simulated and large, so only the allocations themselves limit it.\n\n- `422 AGENT_ALLOCATION_ABOVE_BALANCE`: the holder does not have this much available beyond what its agents already hold.\n- `409 AGENT_NOT_ACTIVE`: the agent or its grant is not active.\n- `409 IDEMPOTENCY_KEY_REUSED`: this key was used with a different body.\n- `403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentAllocationMoveRequest"
              },
              "examples": {
                "monthly": {
                  "summary": "Allocate 200 USDC to the agent",
                  "value": {
                    "idempotency_key": "agent-7-2026-11",
                    "amount": {
                      "amount": "200.00",
                      "currency": "USDC"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A repeat of an allocation already recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAllocation"
                }
              }
            }
          },
          "201": {
            "description": "The allocation was raised.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAllocation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/funding-from-wallet \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"agent-7-2026-11\",\n    \"amount\": {\n      \"amount\": \"200.00\",\n      \"currency\": \"USDC\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/funding-from-wallet\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"agent-7-2026-11\",\n    \"amount\": {\n      \"amount\": \"200.00\",\n      \"currency\": \"USDC\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/funding-from-wallet\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"agent-7-2026-11\",\n        \"amount\": {\n            \"amount\": \"200.00\",\n            \"currency\": \"USDC\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/defunding": {
      "post": {
        "operationId": "defundAgentAllocation",
        "tags": [
          "Agents"
        ],
        "summary": "Give back what the agent has not spent",
        "description": "Lowers the agent's allocation by an amount it has not spent. No money moves: the amount simply stops being set aside for\nthis agent. Works even after the agent or its grant was revoked.\n\n- `422 AGENT_ALLOCATION_INSUFFICIENT`: the agent does not have this much unspent in that currency.\n- `409 IDEMPOTENCY_KEY_REUSED`: this key was used with a different body.\n- `403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentAllocationMoveRequest"
              },
              "examples": {
                "giveBack": {
                  "summary": "Give back 50 USDC",
                  "value": {
                    "idempotency_key": "agent-7-giveback-1",
                    "amount": {
                      "amount": "50.00",
                      "currency": "USDC"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A repeat of a defunding already recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAllocation"
                }
              }
            }
          },
          "201": {
            "description": "The allocation was lowered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAllocation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/defunding \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"agent-7-giveback-1\",\n    \"amount\": {\n      \"amount\": \"50.00\",\n      \"currency\": \"USDC\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/defunding\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"agent-7-giveback-1\",\n    \"amount\": {\n      \"amount\": \"50.00\",\n      \"currency\": \"USDC\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/defunding\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"agent-7-giveback-1\",\n        \"amount\": {\n            \"amount\": \"50.00\",\n            \"currency\": \"USDC\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/allocation": {
      "get": {
        "operationId": "getAgentAllocation",
        "tags": [
          "Agents"
        ],
        "summary": "How much of its allocation the agent has spent",
        "description": "Per currency: what was allocated, what the agent spent from it (including requests still waiting for a person or not yet\nsettled) and what is left. An agent with no allocation returns an empty `balances`. A request the agent opened before the\nallocation existed, and that is still open, counts as spent: `spent` can then be larger than `allocated`, and `available` is zero.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The agent's allocation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAllocation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/allocation \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/allocation\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/allocation\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/wallet": {
      "post": {
        "operationId": "registerAgentWallet",
        "tags": [
          "Agents"
        ],
        "summary": "Give an agent a wallet (bring your own Safe, or a wallet OZAV holds)",
        "description": "Two ways, chosen by `custody`.\n\n**`custody: ozav_enclave`: a wallet OZAV holds for the agent.** OZAV creates, in its own secure signing infrastructure, a\nwallet for this one live agent. The wallet can only sign a USDC payment authorization (x402 and MPP `authorization`) from\nitself, and never one above the per-payment cap of the mandate: that cap is `teto_por_operacao_usd` of the grant's mandate (the\ntighter one, when the agent has its own), read when the wallet is created and sealed inside the wallet, where nothing outside it\ncan change it. The same address serves Base and Polygon. Idempotent: the first call reserves the wallet and answers `202`\n(`status: pending`, no address yet); OZAV then creates it, which is not immediate, so call again later until\n`status: active`, which records the address (`201` once, `200` after) and returns it. The grant's mandate must set a per-operation cap. If the mandate's cap changes later, the wallet keeps\nthe cap it was created with: a payment above it is refused by the wallet, a payment below it is still limited by the mandate; a\nwallet with a new cap is a new wallet and is not offered yet. Only the account holder's organization, with `agent_grants:write`;\nlive agents only; off until the account has it enabled (`503 AGENT_ENCLAVE_WALLETS_NOT_ENABLED`). An agent has either this\nwallet or a Safe, never both.\n\n- `422 AGENT_ENCLAVE_WALLET_LIVE_ONLY`: a sandbox agent's money is simulated; it has no wallet of its own.\n- `422 AGENT_ENCLAVE_WALLET_NEEDS_A_PER_OPERATION_CAP`: the mandate sets no per-operation cap, so there is nothing to seal.\n- `409 AGENT_WALLET_HAS_A_SAFE`, `AGENT_REVOKED`, `AGENT_GRANT_NOT_ACTIVE`, `AGENT_ENCLAVE_WALLET_FAILED` (OZAV replaces the wallet\n  with a new one with the same cap; call again later).\n\n**`custody: bring_your_own`: a Safe the company deployed (non-custodial).** The money stays in a Safe the company deployed, and nothing leaves it without OZAV's signature. The company\ndeploys a Safe 1.4.1 on Base or Polygon with exactly two owners, its own key (or its agent's) and OZAV's policy key, a\nthreshold of 2, no module, and the standard Safe compatibility fallback (it answers EIP-1271 when a USDC payment settles).\nOZAV reads the network and registers the Safe only if all of that holds; nothing is written to any network.\n\nOnly the account holder's organization registers it, with a key that has `agent_grants:write`. Off until bring-your-own\nwallets are enabled for the account (`422 AGENT_SMART_ACCOUNT_NOT_ENABLED`). Once registered, the agent's payments, payouts,\nfundings and allocations are refused with `422 AGENT_SMART_ACCOUNT_SIGNING_UNAVAILABLE` until signing through the Safe is\nswitched on: they never fall back to a custodial wallet. The company's owner must be a key (an address without code).\n\n- `422 SMART_ACCOUNT_NOT_DEPLOYED`, `SMART_ACCOUNT_NOT_A_SAFE`, `SMART_ACCOUNT_THRESHOLD_NOT_2`, `SMART_ACCOUNT_OZAV_NOT_OWNER`,\n  `SMART_ACCOUNT_HAS_MODULES`, `SMART_ACCOUNT_NO_SIGNATURE_HANDLER`, `SMART_ACCOUNT_OWNER_NOT_A_KEY`, `SMART_ACCOUNT_UNREADABLE`: what the network answered\n  does not meet the rule above. Nothing is recorded.\n- `409 AGENT_WALLET_ALREADY_REGISTERED`: the agent already has another Safe on this network, or this Safe is registered for another agent.\n- `409 AGENT_WALLET_HAS_AN_ENCLAVE_WALLET`: the agent already has a wallet OZAV holds for it.\n- `409 AGENT_WALLET_OPEN_CUSTODIAL_ACTIONS`: the agent has a custodial payout awaiting approval or approved; let it finish or expire first.\n- `403 AGENT_WALLET_HOLDER_ONLY`: only the account holder's organization, with `agent_grants:write`.\n- `503 AGENT_POLICY_SIGNER_UNAVAILABLE` or `NETWORK_UNAVAILABLE`: nothing was registered; retry later.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_grants:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentWalletRequest"
              },
              "examples": {
                "safe": {
                  "summary": "Register a Safe on Base",
                  "value": {
                    "custody": "bring_your_own",
                    "safe_address": "0xa52928484b43dfa36f8795340e84cf38a83c8569",
                    "network": "base"
                  }
                },
                "ozav_enclave": {
                  "summary": "A wallet OZAV holds, on Polygon",
                  "value": {
                    "custody": "ozav_enclave",
                    "network": "polygon"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "This Safe was already registered for the agent on this network, or the wallet OZAV holds was already recorded for this network.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWallet"
                }
              }
            }
          },
          "201": {
            "description": "The Safe was verified and registered, or the wallet OZAV holds is active and its address was recorded for this network.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWallet"
                }
              }
            }
          },
          "202": {
            "description": "The wallet OZAV holds is being created (`status` is `pending`). Call again; the address comes once it is active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWallet"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/wallet \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"custody\": \"bring_your_own\",\n    \"safe_address\": \"0xa52928484b43dfa36f8795340e84cf38a83c8569\",\n    \"network\": \"base\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/wallet\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"custody\": \"bring_your_own\",\n    \"safe_address\": \"0xa52928484b43dfa36f8795340e84cf38a83c8569\",\n    \"network\": \"base\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/wallet\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"custody\": \"bring_your_own\",\n        \"safe_address\": \"0xa52928484b43dfa36f8795340e84cf38a83c8569\",\n        \"network\": \"base\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "getAgentWallet",
        "tags": [
          "Agents"
        ],
        "summary": "Which wallet the agent pays from",
        "description": "`custody: ozav` when the agent pays from the account holder's OZAV wallet; `bring_your_own` with the registered Safes when\nthe company brought its own; `ozav_enclave` with the recorded addresses (`enclave_wallets`) when OZAV holds a wallet for the agent.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The agent's wallet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWallet"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/wallet \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/wallet\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/wallet\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/wallet/withdraw": {
      "post": {
        "operationId": "withdrawFromAgentWallet",
        "tags": [
          "Agents"
        ],
        "summary": "Take USDC back from the agent's wallet",
        "description": "The account holder moves USDC out of the wallet OZAV holds for its agent (`custody: ozav_enclave`) and back to the\nholder. Omit `amount` to withdraw the whole balance. The money leaves the agent's address on Polygon for OZAV's treasury,\nsigned inside OZAV's own infrastructure; the holder's balance with OZAV does not change, because the agent's money already\ncounts as the holder's. It is not the agent spending: the mandate budget is not used and no agent action is recorded.\n\nThe wallet signs no single payment above the per-payment limit sealed into it (`per_payment_cap` of\n`POST /agents/{agent_id}/wallet`), so a larger amount is split into parts of at most that limit, run one after the\nother (at most 50 parts). A part waits for the network: when the request runs out of time before every part has started,\nit answers `202` with `status: processing`. Call again with the same `idempotency_key` to carry on from the first part\nthat has not settled; OZAV also carries on by itself. The same key always answers the same withdrawal and never signs a\npart twice.\n\nWhen an agent or its grant is revoked, OZAV withdraws the agent wallet's whole balance on its own (`trigger: revocation`),\nand tries again later when a part was refused. USDC that reaches a revoked agent's wallet after that is withdrawn with\nthis call.\n\nOnly the account holder's organization, with a key that has `agent_grants:write`. Production only. Works after the\nagent or its grant was revoked. Off until the account has it enabled.\n\n- `400 INVALID_AMOUNT`: `amount.amount` is not a positive decimal with at most 6 places.\n- `404 AGENT_NOT_FOUND`: no such agent for this key.\n- `403 AGENT_WALLET_HOLDER_ONLY`: the caller is not the account holder's organization, or the key lacks `agent_grants:write`.\n- `503 AGENT_WALLET_WITHDRAW_NOT_ENABLED`: withdrawals from agent wallets are not enabled yet. Nothing was withdrawn.\n- `422 AGENT_WALLET_NOT_ACTIVE`: the agent has no wallet on Polygon in OZAV's infrastructure (or it is a sandbox agent).\n- `422 AGENT_WALLET_WITHDRAW_ABOVE_BALANCE`, `422 AGENT_WALLET_EMPTY`, `422 AGENT_WALLET_WITHDRAW_TOO_MANY_PARTS`: nothing was withdrawn.\n- `409 AGENT_WALLET_WITHDRAW_PAUSED`: withdrawals are paused right now; nothing was withdrawn, retry later.\n- `409 AGENT_WALLET_WITHDRAW_IN_PROGRESS`: another withdrawal from this wallet is still processing or under review.\n- `409 IDEMPOTENCY_KEY_REUSED`: this key was used with a different body.\n- `503 NETWORK_UNAVAILABLE`: the wallet's balance could not be read; nothing was withdrawn.\n- `422 AGENT_WALLET_WITHDRAW_REFUSED`, `422 AGENT_WALLET_WITHDRAW_ABOVE_WALLET_LIMIT`: a part was refused before it moved;\n  nothing after it runs. `details.withdrawal.settled_amount` says what was withdrawn.\n- `503 AGENT_WALLET_WITHDRAW_OUTCOME_UNKNOWN`: OZAV could not confirm a part and is reviewing it; nothing more runs on its\n  own. Retry with the same key to read it, never with a new one.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_grants:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentWalletWithdrawRequest"
              },
              "examples": {
                "wholeBalance": {
                  "summary": "Withdraw the whole balance",
                  "value": {
                    "idempotency_key": "agent-7-withdraw-1"
                  }
                },
                "someOfIt": {
                  "summary": "Withdraw 25 USDC",
                  "value": {
                    "idempotency_key": "agent-7-withdraw-2",
                    "amount": {
                      "amount": "25.00",
                      "currency": "USDC"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A repeat of a withdrawal that already settled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWalletWithdrawal"
                }
              }
            }
          },
          "201": {
            "description": "Every part settled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWalletWithdrawal"
                }
              }
            }
          },
          "202": {
            "description": "Still processing. Call again with the same `idempotency_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWalletWithdrawal"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/wallet/withdraw \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"agent-7-withdraw-1\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/wallet/withdraw\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"agent-7-withdraw-1\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/wallet/withdraw\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"agent-7-withdraw-1\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/consumptions": {
      "post": {
        "operationId": "recordAgentConsumption",
        "tags": [
          "Agents"
        ],
        "summary": "Record what the agent consumed from a prepaid resource",
        "description": "The agent declares that it used part of a prepaid resource the company registered (`POST\n/agent-grants/{grant_id}/prepaid-resources`). Nothing moves money: the consumption has no dollar amount, counts in no\nspending limit or budget, and its `nature` is `prepaid` (`simulated` in the sandbox). OZAV debits the resource's balance\nin the same step that records the action, so two agents can never consume the same unit and the balance never goes\nbelow zero.\n\nA person approved once, when the company granted the mandate: it needs the `prepaid_consumption` action, and when the\nmandate lists `allowed_domains`, the resource's `seller_domain` must be among them. Inside the mandate the\nconsumption is `executed` at once, with no approval per action.\n\nThe response is the `AgentAction` (`action: prepaid_consumption`) with `consumption`: the resource, the units, and the\nbalance it left (`balance_after`). The same `idempotency_key` with the same body returns the same action (`200`) and\nnever debits twice.\n\n- `422 AGENT_PAYMENT_OUTSIDE_MANDATE`: refused and recorded, with the reasons: from the mandate (`action_not_allowed`,\n  `domain_not_allowed`, `mandate_expired`, ...) or from the resource (`prepaid_balance_insufficient`,\n  `prepaid_resource_expired`, `prepaid_resource_closed`, `prepaid_resource_other_agent`). Nothing was debited.\n- `404 PREPAID_RESOURCE_NOT_FOUND`: no such resource under this agent's grant.\n- `409 AGENT_PAYMENT_CONFLICT`: this `idempotency_key` was used under this grant for a different request.\n- `409 AGENT_GRANT_NOT_ACTIVE` / `409 AGENT_NOT_ACTIVE`: checked before anything is recorded.\n- `403 NOT_THE_GRANTED_PLATFORM`: only the platform this agent belongs to can record its consumptions.\n- `403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentConsumptionRequest"
              },
              "examples": {
                "tokens": {
                  "summary": "The agent used 12,000 tokens",
                  "value": {
                    "idempotency_key": "run-2026-10-13-0001",
                    "resource_id": "ozav_ppr_01JA0000000000000000000000",
                    "units": "12000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The consumption recorded earlier under this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "201": {
            "description": "The consumption was recorded and the resource debited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/consumptions \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"run-2026-10-13-0001\",\n    \"resource_id\": \"ozav_ppr_01JA0000000000000000000000\",\n    \"units\": \"12000\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/consumptions\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"run-2026-10-13-0001\",\n    \"resource_id\": \"ozav_ppr_01JA0000000000000000000000\",\n    \"units\": \"12000\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/consumptions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"run-2026-10-13-0001\",\n        \"resource_id\": \"ozav_ppr_01JA0000000000000000000000\",\n        \"units\": \"12000\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/payees": {
      "post": {
        "operationId": "registerAgentPayee",
        "tags": [
          "Agents"
        ],
        "summary": "Register a beneficiary the agent will pay, inside the mandate",
        "description": "The agent registers a payout destination (a bank account or Pix key) in the company's name, so a later payout can name it.\nA person approved once, when the company granted the mandate: inside `payee_rules` the destination is created with no\napproval per registration, and outside them the request is refused and recorded. Registering moves no money and has no\namount, so no spending limit applies. The body is the one of `POST /payout-destinations` plus your `idempotency_key`;\nthe destination always belongs to the account holder of the grant (there is no `customer_id` to send), and every rule of\n`POST /payout-destinations` applies in full.\n\nThe mandate needs the `payee_registration` action and `payee_rules`; without rules nothing can be registered.\n`payee_rules.holder` decides whose account: `self` accepts only the account holder's own account (the document declared in\n`account_holder.tax_id` must be the one on file for the account holder: a Brazilian holder's `cpf` or `cnpj`, or for a holder\noutside Brazil the tax id or registration on the customer, with the same `country`, the same `type` and the same value; whether\nthe account itself belongs to that holder is checked by the same ownership rules as `POST /payout-destinations`),\n`third_party_identified` also accepts another holder, as long as the request declares a tax id. The rules can also list\n`currencies`, `rails` and `tax_ids` (compared without punctuation) and cap how many destinations the agent registers per\nUTC day with `max_per_day`.\n\nThe response is the `AgentAction` (`action: payee_registration`). When it is `executed`, `payee.id` is the new\ndestination (`ozav_pd_...`). A mandate whose `allowed_payees` holds `{ type: agent_created }` lets a payout name any\ndestination this agent registered, and no other.\n\nThe same `idempotency_key` with the same body returns the same destination (`200`) and never creates a second one.\nIn the sandbox nothing is created: the destination is simulated (`receipt.simulated` is `true`) and the checks that\n`POST /payout-destinations` runs on the account details do not run.\n\n- `503 AGENT_PAYEE_OUTCOME_UNKNOWN`: OZAV could not confirm whether the destination was created. Repeat with the same\n  `idempotency_key`, never a new one; the repeat returns the original destination.\n- `422 AGENT_PAYEE_REFUSED`: refused before it was created. `details.upstream.code` names a problem you can fix (a\n  field, the rail or the currency); any other reason reads `REFUSED`.\n- `422 AGENT_PAYMENT_OUTSIDE_MANDATE`: outside the mandate, with the reasons (`action_not_allowed`, `payee_rules_missing`,\n  `payee_currency_not_allowed`, `payee_rail_not_allowed`, `payee_holder_not_allowed`, `payee_tax_id_not_allowed`,\n  `above_payee_daily`, `payee_unreadable`). The refusal is recorded. It is also returned, without reasons, when the grant, the\n  agent or an agent above it is revoked or suspended before the destination is created; nothing was created.\n- `409 AGENT_MANDATE_CHANGED`: the mandate changed while this registration was being processed.\n- `409 AGENT_PAYMENT_CONFLICT`: this `idempotency_key` was used under this grant for a different registration, or by a\n  different agent.\n- `409 AGENT_PAYMENT_FAILED`: a repeat of a registration that failed. Use a new key to try again.\n- `409 AGENT_GRANT_NOT_ACTIVE` / `409 AGENT_NOT_ACTIVE`: checked before anything is recorded.\n- `403 NOT_THE_GRANTED_PLATFORM`: only the platform this agent belongs to can register payees for it.\n- `403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n- `503 AGENT_PAYEES_NOT_ENABLED`: agent actions are not enabled yet in production; nothing was created. The sandbox\n  does not need it.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentPayeeRequest"
              },
              "examples": {
                "pix": {
                  "summary": "Register a supplier's Pix key",
                  "value": {
                    "idempotency_key": "supplier-acme-001",
                    "currency": "BRL",
                    "rail": "pix",
                    "account_holder": {
                      "name": "Acme Fornecedora Ltda",
                      "type": "business",
                      "tax_id": {
                        "country": "BR",
                        "type": "cnpj",
                        "value": "11222333000181"
                      }
                    },
                    "details": {
                      "pix_key": "financeiro@acme.example"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recorded registration (a retry of one already created).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "201": {
            "description": "The destination was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/payees \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"supplier-acme-001\",\n    \"currency\": \"BRL\",\n    \"rail\": \"pix\",\n    \"account_holder\": {\n      \"name\": \"Acme Fornecedora Ltda\",\n      \"type\": \"business\",\n      \"tax_id\": {\n        \"country\": \"BR\",\n        \"type\": \"cnpj\",\n        \"value\": \"11222333000181\"\n      }\n    },\n    \"details\": {\n      \"pix_key\": \"financeiro@acme.example\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/payees\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"supplier-acme-001\",\n    \"currency\": \"BRL\",\n    \"rail\": \"pix\",\n    \"account_holder\": {\n      \"name\": \"Acme Fornecedora Ltda\",\n      \"type\": \"business\",\n      \"tax_id\": {\n        \"country\": \"BR\",\n        \"type\": \"cnpj\",\n        \"value\": \"11222333000181\"\n      }\n    },\n    \"details\": {\n      \"pix_key\": \"financeiro@acme.example\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/payees\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"supplier-acme-001\",\n        \"currency\": \"BRL\",\n        \"rail\": \"pix\",\n        \"account_holder\": {\n            \"name\": \"Acme Fornecedora Ltda\",\n            \"type\": \"business\",\n            \"tax_id\": {\n                \"country\": \"BR\",\n                \"type\": \"cnpj\",\n                \"value\": \"11222333000181\",\n            },\n        },\n        \"details\": {\n            \"pix_key\": \"financeiro@acme.example\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/accounts": {
      "post": {
        "operationId": "openAgentAccount",
        "tags": [
          "Agents"
        ],
        "summary": "Open a named fiat account for the account holder, inside the mandate",
        "description": "The agent opens a named fiat account (the same account `POST /accounts` opens) for the account holder of its grant.\nA person approved once, when the company granted the mandate: inside `account_rules` the account is requested with no\napproval per action, and outside them the request is refused and recorded. Opening moves no money and has no amount,\nso no spending limit applies. The body is the one of `POST /accounts` plus your `idempotency_key`; the account always\nbelongs to the account holder of the grant (there is no `customer_id` to send), and every rule of `POST /accounts`\napplies in full: the holder's verification, the corridors OZAV serves and the account institution's own checks. The\nagent cannot skip any of them.\n\nThe mandate needs the `account_opening` action and `account_rules`; without rules nothing can be opened. The rules can\nlist `currencies`, `countries` and `rails`, and must cap how many accounts the agents of the grant open with\n`max_accounts` (over the grant's life). When the rules list countries or rails, the request must name them.\n\nThe response is the `AgentAction` (`action: account_opening`) with `named_account`, the account as `GET /accounts/{account_id}`\nshows it (it may still be `pending` while the institution reviews it). Only accounts the agent itself opened are its\nown: an account counts only when it comes back carrying this action's id in its metadata (`ozav_agent_action`), which\n`POST /accounts` writes when it creates the account. If the account holder already has an account in that corridor\n(or one appears from elsewhere at the same time), the answer is `409 AGENT_ACCOUNT_ALREADY_EXISTS` and that account\nstays out of the agent's reach.\n\nThe same `idempotency_key` with the same body returns the same account (`200`) and never opens a second one. In the\nsandbox nothing is opened: the account is simulated (`receipt.simulated` is `true`). An opening whose outcome OZAV could\nnot confirm is settled by OZAV within minutes when the account it created exists (`executed`, `receipt.reconciled`), or\n`expired` after 24 hours when none appeared, which gives its `max_accounts` slot back. Agents cannot pay out from these\naccounts yet.\n\n- `422 AGENT_ACCOUNT_HOLDER_NOT_ELIGIBLE`: the account holder cannot have this account right now. `details.reason` is\n  `holder_verification_pending` (the holder still has to finish verification or activation, or the institution is\n  reviewing it) or `holder_not_eligible` (refused, not enabled or deactivated). Nothing in the request can fix it.\n- `422 AGENT_ACCOUNT_REFUSED`: refused before it was opened. `details.upstream.code` names a problem you can fix (the\n  rail or the corridor); any other reason reads `REFUSED`.\n- `409 AGENT_ACCOUNT_ALREADY_EXISTS`: the account holder already had this account.\n- `503 AGENT_ACCOUNT_OUTCOME_UNKNOWN`: OZAV could not confirm whether the account was requested (or a provisioning is\n  still under way). Repeat with the same `idempotency_key`, never a new one.\n- `422 AGENT_PAYMENT_OUTSIDE_MANDATE`: outside the mandate, with the reasons (`action_not_allowed`,\n  `account_rules_missing`, `account_currency_not_allowed`, `account_country_not_allowed`, `account_rail_not_allowed`,\n  `above_max_accounts`, `accounts_unreadable`). The refusal is recorded.\n- `409 AGENT_MANDATE_CHANGED`, `409 AGENT_PAYMENT_CONFLICT`, `409 AGENT_PAYMENT_FAILED`, `409 AGENT_GRANT_NOT_ACTIVE`,\n  `409 AGENT_NOT_ACTIVE`, `403 NOT_THE_GRANTED_PLATFORM`, `403 AGENT_PLATFORM_SUSPENDED`: as on `POST /agents/{agent_id}/payees`.\n- `503 AGENT_ACCOUNTS_NOT_ENABLED`: agent accounts are not enabled yet; nothing was requested or recorded.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentAccountOpeningRequest"
              },
              "examples": {
                "brl": {
                  "summary": "Open a BRL account with Pix for the company",
                  "value": {
                    "idempotency_key": "brl-account-001",
                    "currency": "BRL",
                    "country": "BR",
                    "rail": "pix"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recorded opening (a retry of one already opened).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAccountActionWithAccount"
                }
              }
            }
          },
          "201": {
            "description": "The account was requested and exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAccountActionWithAccount"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/accounts \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"brl-account-001\",\n    \"currency\": \"BRL\",\n    \"country\": \"BR\",\n    \"rail\": \"pix\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/accounts\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"brl-account-001\",\n    \"currency\": \"BRL\",\n    \"country\": \"BR\",\n    \"rail\": \"pix\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/accounts\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"brl-account-001\",\n        \"currency\": \"BRL\",\n        \"country\": \"BR\",\n        \"rail\": \"pix\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listAgentAccounts",
        "tags": [
          "Agents"
        ],
        "summary": "List the accounts the agent opened",
        "description": "The accounts this agent opened, newest first (up to 50; `has_more` says when there are more), with `named_account` as\n`GET /accounts/{account_id}` shows it now (`null` when it could not be read right now). Refused or failed openings are\nnot accounts: read them in `GET /agents/{agent_id}/actions`. The account holder's organization can always read it; the\nagent's platform only while it can still act for the holder.\n\n- `409 AGENT_GRANT_NOT_ACTIVE` / `409 AGENT_NOT_ACTIVE` / `403 AGENT_PLATFORM_SUSPENDED`: for the platform, once the\n  grant, the agent (or one above it) or the platform is no longer active.\n- `503 AGENT_ACCOUNTS_NOT_ENABLED`: agent accounts are not enabled yet.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The agent's account openings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAccountList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/accounts \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/accounts\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/accounts\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/accounts/{account_id}/movements": {
      "get": {
        "operationId": "listAgentAccountMovements",
        "tags": [
          "Agents"
        ],
        "summary": "Read the statement of an account the agent opened",
        "description": "The statement of an account this agent opened, exactly as `GET /accounts/{account_id}/movements` returns it, read live.\nAny other account id (the account holder's own accounts included) answers `404 AGENT_ACCOUNT_NOT_FOUND`. In the\nsandbox the statement is empty and `simulated` is `true`. The account holder's organization can always read it; the\nagent's platform only while it can still act for the holder.\n\n- `409 AGENT_GRANT_NOT_ACTIVE` / `409 AGENT_NOT_ACTIVE` / `403 AGENT_PLATFORM_SUSPENDED`: for the platform, once the\n  grant, the agent (or one above it) or the platform is no longer active.\n- `501 ACCOUNT_MOVEMENTS_NOT_AVAILABLE` / `503 ACCOUNT_MOVEMENTS_UNAVAILABLE`: as on `GET /accounts/{account_id}/movements`.\n- `503 AGENT_ACCOUNTS_NOT_ENABLED`: agent accounts are not enabled yet.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The account id (`ozav_acc_...`) of an account this agent opened.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The account's credits, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AccountMovement"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "simulated": {
                      "type": "boolean",
                      "description": "`true` in the sandbox: nothing was read from an institution."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "501": {
            "$ref": "#/components/responses/NotImplementedYet"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/accounts/{account_id}/movements \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/accounts/{account_id}/movements\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/accounts/{account_id}/movements\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/fundings": {
      "post": {
        "operationId": "requestAgentFunding",
        "tags": [
          "Agents"
        ],
        "summary": "Ask for a charge that becomes stablecoin in the company's wallet",
        "description": "The agent asks for a charge that turns into stablecoin in the company's managed wallet: the\non-ramp. A charge is either reais by Pix (`rail: pix`, the Pix copy-and-paste code in\n`funding.payment_instructions.br_code`) or US dollars by wire (`rail: wire`, `fedwire` or `swift`: the bank\ncoordinates and the `reference` to write in the wire's memo, in `funding.payment_instructions`). OZAV quotes\nit, checks the grant's mandate and the agent's own, and returns the instructions for you to hand to whoever pays.\nIn the sandbox the charge is simulated (fixed rates of R$ 5.00 and US$ 1.00 per US dollar of stablecoin, a Pix\ncode that starts with `SIMULATED-PIX-` or bank fields marked `SIMULATED`, which no bank accepts;\n`funding.simulated` is `true`): finish it with `POST /agent-actions/{action_id}/simulate`.\n\n**The agent does not move money here, and the payer is the company.** A Pix charge is locked to the\ncompany's own CPF or CNPJ, so a payment from anyone else does not settle. A wire is credited only when the\nbank statement shows the account holder as the sender; a wire from anyone else, or one whose statement does\nnot say who sent it, is held for review and never reaches the wallet. An account that has not turned the\npayer lock on gets `422 AGENT_FUNDING_PAYER_LOCK_REQUIRED` and nothing is quoted. The money only\nmoves when the company pays, and the stablecoin lands in the account holder's own wallet\n(`target.wallet_id` must be theirs, otherwise `404 WALLET_NOT_FOUND`), never in a wallet of the agent\nor of another customer.\n\n**A funding is not spending.** It does not count against `per_payment_limit_usd`, `daily_limit_usd`,\n`total_budget_usd`, `recurring_budget` or `human_approval_above_usd`, and it never waits for a person. The mandate\nneeds the `fiat_funding` action, may list the corridor (`BRL>USDC`, `USD>USDC`), asset and network, and\nlimits it with `funding_limits` (`per_request_usd`, `daily_usd`). The dollar amount checked is what\nthe quote delivers in stablecoin. A charge that expires unpaid releases the day's limit.\n\nThe response is the `AgentAction` (`action: fiat_funding`) with the charge in `funding`. Its\n`status` walks `awaiting_payment` (the charge exists), then `settled` once the company paid and\nthe stablecoin arrived, or `returned` if the charge expired or was cancelled unpaid. Each change\narrives as the webhook `agent_action.updated`. A charge rejected at creation is recorded\n`failed` (the response is still `201`, with `status: failed`). A transfer that fails or needs review\nlater stays `awaiting_payment` until OZAV reviews it. The charge is created only if the grant, the mandate and\nthe agent's lineage still hold at that moment (checked in one database transaction that also marks the\ncharge as dispatched); a repeat after dispatch completes it even if the grant was revoked afterwards, because the\ncharge may already exist. The Pix code (`br_code`, `qr_code_image`) is only\nreturned while the charge waits for payment; once it is settled, returned or expired the same object\ncarries `payment_instructions: null`. Without `funding_limits` a funding has no amount limit: set\nthem.\n\nThe same `idempotency_key` with the same body returns the same charge (`200`) and never creates a\nsecond one.\n\n- `503 AGENT_FUNDING_OUTCOME_UNKNOWN`: OZAV could not confirm whether the charge was created. Repeat\n  with the same `idempotency_key`, never a new one; the repeat returns the original charge.\n- `404 WALLET_NOT_FOUND`: `target.wallet_id` does not exist for this account holder (a wallet of\n  another customer of the company reads the same). Nothing was quoted.\n- `422 AGENT_FUNDING_PAYER_LOCK_REQUIRED`: the Pix payer lock is not turned on for this account.\n- `403 NOT_THE_GRANTED_PLATFORM`: only the platform this agent belongs to can request fundings for it.\n- `409 AGENT_PAYMENT_EXPIRED`: a repeat of a funding that expired before it was used. Use a new key.\n- `422 AGENT_FUNDING_REFUSED`: refused before any charge existed. `details.upstream.code` names a\n  problem you can fix (amount, wallet, network); any other reason reads `REFUSED`.\n- `422 AGENT_FUNDING_QUOTE_UNAVAILABLE`: no quote for this amount and asset right now. A quote whose\n  origin is not the reais you asked for, or whose asset is not the one you asked for, is never used.\n- `422 AGENT_PAYMENT_OUTSIDE_MANDATE`: outside the mandate, with the reasons (`action_not_allowed`,\n  corridor, asset, network, `above_funding_per_request`, `above_funding_daily`, `funding_unreadable`). The refusal is\n  recorded. It is also returned, without reasons, when the grant, the agent or an agent above it is\n  revoked or suspended between the quote and the charge; nothing was created.\n- `409 AGENT_MANDATE_CHANGED`: the mandate changed while this funding was being processed.\n- `409 AGENT_PAYMENT_CONFLICT`: this `idempotency_key` was used under this grant for a different\n  funding (another amount, wallet or asset), or by a different agent.\n- `409 AGENT_PAYMENT_FAILED`: a repeat of a funding that failed, or whose charge expired unpaid\n  (`status: returned`). Use a new key to try again.\n- `409 AGENT_GRANT_NOT_ACTIVE` / `409 AGENT_NOT_ACTIVE`: checked before quoting.\n- `403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n- `503 AGENT_FUNDINGS_NOT_ENABLED`: agent fundings are not enabled yet in production; nothing was quoted. The\n  sandbox does not need it.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentFundingRequest"
              },
              "examples": {
                "pix": {
                  "summary": "Ask for a R$ 500,00 Pix charge that becomes USDC on Polygon",
                  "value": {
                    "idempotency_key": "topup-2026-1012",
                    "amount": {
                      "amount": "500.00",
                      "currency": "BRL"
                    },
                    "rail": "pix",
                    "target": {
                      "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2",
                      "currency": "USDC",
                      "chain": "polygon"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recorded funding (a retry of one already created).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "201": {
            "description": "The Pix charge was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/fundings \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"topup-2026-1012\",\n    \"amount\": {\n      \"amount\": \"500.00\",\n      \"currency\": \"BRL\"\n    },\n    \"rail\": \"pix\",\n    \"target\": {\n      \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2\",\n      \"currency\": \"USDC\",\n      \"chain\": \"polygon\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/fundings\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"topup-2026-1012\",\n    \"amount\": {\n      \"amount\": \"500.00\",\n      \"currency\": \"BRL\"\n    },\n    \"rail\": \"pix\",\n    \"target\": {\n      \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2\",\n      \"currency\": \"USDC\",\n      \"chain\": \"polygon\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/fundings\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"topup-2026-1012\",\n        \"amount\": {\n            \"amount\": \"500.00\",\n            \"currency\": \"BRL\",\n        },\n        \"rail\": \"pix\",\n        \"target\": {\n            \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V2\",\n            \"currency\": \"USDC\",\n            \"chain\": \"polygon\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/payments": {
      "post": {
        "operationId": "requestAgentPayment",
        "tags": [
          "Agents"
        ],
        "summary": "Pay a resource's payment challenge",
        "description": "Send the payment challenge your agent received (x402: the `PAYMENT-REQUIRED`\nheader or, for x402 v1, the 402 body; MPP: the `WWW-Authenticate` header). OZAV picks an option\nit can pay (native USDC on Polygon, Base or Ethereum), checks the grant's mandate and the\nagent's own, and records the request.\n\n- Within the mandate: OZAV reserves the amount, signs the payment authorization from the\n  company's wallet and returns it in `payment`: the header name and value to send back to\n  the resource (x402: `PAYMENT-SIGNATURE`, or `X-PAYMENT` for v1; MPP: `Authorization`). The\n  authorization is valid for five minutes and is delivered once: a retry with the same\n  `idempotency_key` returns `409 AGENT_PAYMENT_ALREADY_SIGNED`. In production, while signing\n  is not enabled the response is `503 AGENT_PAYMENT_SIGNING_UNAVAILABLE`, nothing is signed and\n  the budget is released. On Cloudflare pay-per-crawl (x402 `batch-settlement`, network\n  `cloudflare:402`, in whole US cents), nothing moves on-chain: OZAV debits the company's balance\n  once, signs the request with its agent identity key, and `payment.headers` carries the four\n  headers to send, valid for 60 seconds; the mandate must allow network `cloudflare:402` and\n  asset `USD`, and `503 CLOUDFLARE_PAYMENTS_UNAVAILABLE` means it is not enabled yet (nothing was\n  charged). The same applies to Cloudflare's current form, `protocol: pay_per_crawl`: send the page\n  URL as `resource_url` and the `crawler-price` header of its 402 as `crawler_price` (`USD 0.01`, up\n  to 2 decimals, in whole US cents); `payment.header_name` is `crawler-exact-price` (its value equals\n  your `crawler_price`), the four headers go in `payment.headers`, and the site then answers 200 with\n  `crawler-charged`. In the sandbox, `payment.simulated` is `true`, the signature is all\n  zeros, and the company needs no wallet: OZAV signs from a stable sandbox address for it, so\n  you can integrate the whole flow before going live.\n- Above the approval threshold: `202`, waiting for a person at the company to approve it in\n  the OZAV portal (with their authenticator code). The decision arrives as the webhook\n  `agent_action.updated` (`authorized` or `refused`). Once approved, repeat this request with the\n  same `idempotency_key` and the same challenge to receive the signature; a challenge with a\n  different amount, network or payee is refused with `409 CHALLENGE_DIFFERS_FROM_APPROVED`.\n  A request nobody decides within 24 hours expires, and so does an approved request nobody\n  repeats within 24 hours. If the mandate changed after the approval: `409\n  AGENT_MANDATE_CHANGED`; if the grant, the agent or the mandate is no longer in force: `422\n  AGENT_PAYMENT_OUTSIDE_MANDATE`. Nothing is signed in either case.\n- Outside the mandate: `422 AGENT_PAYMENT_OUTSIDE_MANDATE`, with the reasons (among them\n  `above_recurring_budget` when the amount would pass the mandate's recurring budget for the current window, and\n  `above_agent_allocation` when the agent has an allocation and the amount does not fit in what is left of it). The refusal is\n  recorded. An agent whose parent agent is not active is outside the mandate too.\n\nRefusals after the mandate, each with nothing signed and the budget released:\n`422 AGENT_PAYMENT_NOT_PERMITTED` (this payment cannot be made right now: the reason may be the\ncompany's account or the payee, and it is never given; the message is fixed),\n`422 WALLET_QUORUM_REQUIRED` (the company's wallet policy wants several keys for this amount),\n`422 INSUFFICIENT_ONCHAIN_BALANCE` (the wallet's balance, minus authorizations it already signed\nand nobody used yet, does not cover it), `503 SCREENING_UNAVAILABLE` (the payee could not be\nchecked; retry with a new key), `422 COSIGNER_REFUSED`, `403 CLIENT_COSIGNER_DENIED` and\n`503 CLIENT_COSIGNER_UNAVAILABLE` (the company's own co-signer refused or did not answer; this one\napplies in the sandbox too), and `409 CHALLENGE_ALREADY_PAID` (the same MPP challenge was already\nsigned under another key; a challenge whose earlier attempt failed or expired unused can be paid\nagain). These messages are fixed: they never carry a compliance, screening or co-signer reason.\n\nThe paying company's wallet limits apply too, after the mandate and before anything is signed:\n`422 AMOUNT_ABOVE_PER_TRANSACTION_LIMIT`, `422 WALLET_DAILY_LIMIT_EXCEEDED` (the daily window\ncounts the wallet's agent payments and its other sends together) and\n`422 DESTINATION_NOT_ALLOWLISTED` (a payee outside the wallet's approved destinations, above the\nwallet's automatic amount). A payment a person approved in the portal is not held to the\napproved-destinations rule; the per-payment and daily limits still apply. `503\nWALLET_LIMITS_UNAVAILABLE` means the limits could not be read; nothing was signed. These\nrefusals do not reveal the company's limits or what it has spent; the request ends `failed`.\n\nThe same `idempotency_key` returns the same recorded request and never counts twice. It belongs\nto the grant: another agent using it gets `409 AGENT_PAYMENT_CONFLICT`. A repeat while the first\nrequest is still running gets `409 AGENT_PAYMENT_IN_PROGRESS`; a repeat of a request that failed\nor expired gets `409 AGENT_PAYMENT_FAILED` or `409 AGENT_PAYMENT_EXPIRED` (a returned payout also gets `AGENT_PAYMENT_FAILED`) - use a new key.\n`503 AGENT_PAYMENT_OUTCOME_UNKNOWN`: OZAV could not confirm the signature; the reservation is\nheld until the authorization expires. Do not retry under a new key.\nOZAV charges the account holder a spread on every payment (`spread` on the request, 5 bps by default)\non top of the amount paid; the balance must cover both (`422 LEDGER_INSUFFICIENT` otherwise).\n`503 AGENT_SPREAD_UNAVAILABLE`: the spread could not be determined right now; nothing was signed or\nheld. Retry shortly with a new key.\n`403 AGENT_PLATFORM_SUSPENDED`: your platform is suspended.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agent_payments:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentPaymentRequest"
              },
              "examples": {
                "x402": {
                  "summary": "Pay an x402 challenge",
                  "value": {
                    "protocol": "x402",
                    "idempotency_key": "task-8812-step-3",
                    "payment_required_header": "eyJ4NDAyVmVyc2lvbiI6MiwiYWNjZXB0cyI6W119"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recorded payment request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "202": {
            "description": "Recorded and waiting for a person at the company to approve.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/payments \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"protocol\": \"x402\",\n    \"idempotency_key\": \"task-8812-step-3\",\n    \"payment_required_header\": \"eyJ4NDAyVmVyc2lvbiI6MiwiYWNjZXB0cyI6W119\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/payments\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"protocol\": \"x402\",\n    \"idempotency_key\": \"task-8812-step-3\",\n    \"payment_required_header\": \"eyJ4NDAyVmVyc2lvbiI6MiwiYWNjZXB0cyI6W119\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/payments\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"protocol\": \"x402\",\n        \"idempotency_key\": \"task-8812-step-3\",\n        \"payment_required_header\": \"eyJ4NDAyVmVyc2lvbiI6MiwiYWNjZXB0cyI6W119\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-actions/{action_id}/exceptions": {
      "post": {
        "operationId": "openAgentActionException",
        "tags": [
          "Agents"
        ],
        "summary": "Open an exception case on an agent action",
        "description": "Open a case when an agent payment went wrong: the resource was not delivered\n(`not_delivered`), the amount is wrong (`wrong_amount`), it was paid twice\n(`duplicate_payment`), or the company did not intend it (`not_authorized`). The platform or the\ncompany can open it, only where money moved or may have moved (a signed, settled or executed\naction; otherwise `409 AGENT_ACTION_NOT_DISPUTABLE`). The body takes a code, never free text.\n\nThe case gets a due date (5 days) and goes to OZAV's queue. When it is opened, OZAV records the\n`evidence` from the action as it was at that moment (mandate version and its hash, decision and\nreasons, receipt, amounts, timestamps) with `evidence_sha256`; nobody can change it afterwards.\n\n`status` walks forward only: `open`, then `under_review` once a person at OZAV takes it, then\n`resolved_refunded` or `resolved_denied`. A resolved case never reopens. A resolution carries the\n`remedy`: the channel the money takes back (`pix_med` for a Pix payout or funding,\n`manual_transfer`, or `onchain_payee_block` for a stablecoin payment, which cannot be reversed on\nchain, so the payee is blocked in the mandate instead) and OZAV's protocol `reference`. A denial\ncarries `channel: none`. Each change arrives as the webhook `agent_action.updated`, with the case in\n`exception`.\n\nOne case per action: opening again returns the same case.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "action_id",
            "in": "path",
            "required": true,
            "description": "The agent action id (`ozav_aga_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "reason_code"
                ],
                "properties": {
                  "reason_code": {
                    "type": "string",
                    "enum": [
                      "not_delivered",
                      "wrong_amount",
                      "duplicate_payment",
                      "not_authorized",
                      "other"
                    ]
                  }
                }
              },
              "examples": {
                "not_delivered": {
                  "summary": "Paid, and the resource never delivered",
                  "value": {
                    "reason_code": "not_delivered"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The case already open for this action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentActionException"
                }
              }
            }
          },
          "201": {
            "description": "The case was opened.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentActionException"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-actions/{action_id}/exceptions \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"reason_code\": \"not_delivered\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-actions/{action_id}/exceptions\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"reason_code\": \"not_delivered\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agent-actions/{action_id}/exceptions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"reason_code\": \"not_delivered\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "getAgentActionException",
        "tags": [
          "Agents"
        ],
        "summary": "Read the exception case of an agent action",
        "description": "The case, its evidence and its current status (`open`, `under_review`, `resolved_refunded`,\n`resolved_denied`). `404 AGENT_EXCEPTION_NOT_FOUND` when the action has no case.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "action_id",
            "in": "path",
            "required": true,
            "description": "The agent action id (`ozav_aga_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The case.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentActionException"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-actions/{action_id}/exceptions \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-actions/{action_id}/exceptions\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agent-actions/{action_id}/exceptions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-actions/{action_id}/simulate": {
      "post": {
        "operationId": "simulateAgentAction",
        "tags": [
          "Agents"
        ],
        "summary": "Finish a simulated payout, funding or stablecoin payment in the sandbox",
        "description": "In the sandbox, an agent's payout and funding run the whole decision path (mandate, limits, approval\nthreshold, budget, idempotency, webhooks) with simulated money, and nothing ever pays a Pix charge or settles a\ntransfer. Ask for the outcome you want to test:\n\n- `settled`: a funding becomes `settled` (the holder paid and the stablecoin arrived); a payout becomes `settled`\n  (its transfer completed), as OZAV does for a live payout.\n- `returned`: a funding or a payout becomes `returned` (the charge expired unpaid, or the transfer was returned).\n  That releases the budget or the funding limit exactly as it does in production.\n\nA stablecoin payment (x402 or MPP) stops at `signed` in the sandbox, because nothing is broadcast, and a signed payment holds\nthe budget and the agent's allocation until it ends. Finish it the same way:\n\n- `settled`: a signed payment becomes `settled` (the resource used the authorization; the spend stays counted).\n- `expired` (or `returned`): a signed payment becomes `expired` (the authorization was never used); the budget and the\n  allocation are released. `expired` only applies to a stablecoin payment.\n\nThe same call twice answers the same result. The webhook `agent_action.updated` goes out once, from the call that\nchanged the action. Only the platform the agent belongs to can call it, and only on a sandbox action.\n\n- `422 AGENT_SIMULATION_SANDBOX_ONLY`: a live action finishes with its real transfer.\n- `409 AGENT_ACTION_NOT_SIMULABLE`: only a payout or a funding whose simulated transfer was created\n  (`executed` or `awaiting_payment`) or a `signed` stablecoin payment can be simulated, or the outcome contradicts what\n  already happened (a settled funding cannot be returned, a settled payment cannot expire).\n- `403 NOT_THE_GRANTED_PLATFORM`: only the platform this agent belongs to can simulate its actions.\n- `404 AGENT_ACTION_NOT_FOUND`: the action does not exist for this platform.\n- `400`: `outcome` is missing or is not `settled` or `returned`.\n\nThe `transfer_id` of a simulated payout or funding (`ozav_tr_sim_...`) is synthetic: no transfer with that id\ncan be read from `/transfers`. Its receipt says `simulated: true`.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:write"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "action_id",
            "in": "path",
            "required": true,
            "description": "The agent action id (`ozav_aga_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "outcome"
                ],
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "settled",
                      "returned",
                      "expired"
                    ]
                  }
                }
              },
              "examples": {
                "paid": {
                  "summary": "The holder paid the simulated Pix charge",
                  "value": {
                    "outcome": "settled"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The action after the simulation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-actions/{action_id}/simulate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"outcome\": \"settled\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-actions/{action_id}/simulate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"outcome\": \"settled\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/agent-actions/{action_id}/simulate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"outcome\": \"settled\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agent-actions/{action_id}": {
      "get": {
        "operationId": "getAgentAction",
        "tags": [
          "Agents"
        ],
        "summary": "Retrieve an agent action",
        "description": "One recorded request: the mandate version it was checked against, the decision,\nthe reasons, the status and, once settled, the receipt.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "action_id",
            "in": "path",
            "required": true,
            "description": "The action id (`ozav_aga_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentAction"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agent-actions/{action_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agent-actions/{action_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agent-actions/{action_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/agents/{agent_id}/actions": {
      "get": {
        "operationId": "listAgentActions",
        "tags": [
          "Agents"
        ],
        "summary": "List an agent's actions",
        "description": "The agent's evidence: every payment and payout it requested, newest first (by\nid, to the millisecond), each with the mandate version it was checked against, the decision,\nthe reasons, the status and the receipt - the same object as `GET /agent-actions/{action_id}`.\nThe platform and the company can read it. Filter by `status` with the public name\n(`refused`, `awaiting_approval`, `authorized`, `signed`, `settled`, `executed`, `awaiting_payment`,\n`returned`, `expired`, `failed`); `awaiting_payment` is a funding whose Pix charge waits for the payment; an unknown name answers `400 INVALID_STATUS_FILTER`. Follow `next_cursor` while\n`has_more` is true.\n",
        "x-ozav-entitlement": "agents",
        "x-ozav-scopes": [
          "agents:read"
        ],
        "x-ozav-availability": "beta",
        "parameters": [
          {
            "name": "agent_id",
            "in": "path",
            "required": true,
            "description": "The agent id (`ozav_agt_...`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "nature",
            "in": "query",
            "required": false,
            "description": "Only actions whose money has this nature. An unknown value answers `400 INVALID_NATURE_FILTER`.",
            "schema": {
              "type": "string",
              "enum": [
                "new_money",
                "simulated",
                "prepaid"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only actions in this public status.",
            "schema": {
              "type": "string",
              "enum": [
                "refused",
                "awaiting_approval",
                "authorized",
                "signed",
                "settled",
                "executed",
                "awaiting_payment",
                "returned",
                "expired",
                "failed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the agent's actions.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "object": {
                          "type": "string",
                          "const": "list",
                          "description": "Always the literal `list`."
                        },
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AgentAction"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/agents/{agent_id}/actions \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/agents/{agent_id}/actions\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/agents/{agent_id}/actions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/verifications": {
      "get": {
        "operationId": "listVerifications",
        "tags": [
          "Verifications"
        ],
        "summary": "List verification sessions",
        "description": "Lists verification sessions, newest first, with the standard cursor\npagination. Filter to one customer with the `customer_id` query\nparameter to recover an existing verification id after `POST\n/verifications` or after a duplicate `VERIFICATION_ALREADY_EXISTS`\nresponse. Returns a `Page` of `Verification`.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "verifications:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated verification sessions.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Verification"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of verifications",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "verification",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "status": "pending",
                          "checks": {
                            "kyc_document": {
                              "outcome": "not_started"
                            },
                            "liveness": {
                              "outcome": "not_started"
                            }
                          },
                          "created_at": "2026-07-12T18:00:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/verifications \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/verifications\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/verifications\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "post": {
        "operationId": "createVerification",
        "tags": [
          "Verifications"
        ],
        "summary": "Start a verification session for a customer",
        "description": "Starts one or more verification checks (hosted session when the check\nrequires end-user interaction). Check kinds are the canonical OZAV\ncapability taxonomy.\n\nVerification records persist and can be listed with `GET\n/verifications`. Hosted `session_url` issuance is gated by your\norganization's capabilities and may be absent.\n\nSandbox: a test key (`ozav_sk_test_`) never receives a `session_url` and\nnever starts a real identity check - a sandbox verification has no real\nend-user to redirect. Create the verification, then drive it to its end\nstate with `POST /verifications/{id}/simulate`.\n\nReliance: for a customer created with `verification_mode: reliance`\n(your organization has the `kyc_reliance` product), your organization\nalready identified the end user, so OZAV does not start a hosted\nidentity, liveness or business verification session. Asking for any\nhosted check (`kyc_document`, `liveness`, `face_match`,\n`age_estimation`, `kyb`, `ubo`) answers `409\nVERIFICATION_NOT_REQUIRED_UNDER_RELIANCE`, in sandbox and live alike,\nand nothing is created or charged. Send the customer data and the\nidentity document images with `POST /customers/{customer_id}/documents`\ninstead; OZAV screens the customer and decides on its own. Checks OZAV\nruns itself (`aml_name_screening`, `economic_capacity`) can still be\nrequested here.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "verifications:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "capabilities"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "The `ozav_cus_` id of the customer to verify."
                  },
                  "capabilities": {
                    "type": "array",
                    "minItems": 1,
                    "description": "The checks to run, from the canonical capability taxonomy (see `CapabilityKind`).",
                    "items": {
                      "$ref": "#/components/schemas/CapabilityKind"
                    }
                  },
                  "redirect_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Where to return the end-user after a hosted verification session, when one is used."
                  }
                }
              },
              "examples": {
                "kyc": {
                  "summary": "Start KYC document + liveness + name screening",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "capabilities": [
                      "kyc_document",
                      "liveness",
                      "aml_name_screening"
                    ],
                    "redirect_url": "https://acme.example/kyc/return"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Verification session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                },
                "examples": {
                  "pending": {
                    "summary": "Session created - checks not started yet",
                    "value": {
                      "id": "ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "verification",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "pending",
                      "checks": {
                        "kyc_document": {
                          "outcome": "not_started"
                        },
                        "liveness": {
                          "outcome": "not_started"
                        },
                        "aml_name_screening": {
                          "outcome": "not_started"
                        }
                      },
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`VERIFICATION_NOT_REQUIRED_UNDER_RELIANCE` - the customer is under\nreliance and the request asked for a hosted check. No session was\nstarted and nothing was stored. `error.details.hosted_capabilities`\nlists the checks refused. Do not send the end user to a\nverification link: send the identity document images with `POST\n/customers/{customer_id}/documents`. Retrying the same body returns\nthe same error.\n\n`VERIFICATION_ALREADY_EXISTS` - a verification already exists for\nthis customer's hosted session; recover it with `GET\n/verifications?customer_id=`.\n",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "reliance": {
                    "summary": "Customer under reliance - no hosted session",
                    "value": {
                      "error": {
                        "type": "conflict_error",
                        "code": "VERIFICATION_NOT_REQUIRED_UNDER_RELIANCE",
                        "message": "This customer is under reliance: your organization verified their identity, so OZAV does not start a hosted identity, liveness or business verification session for them, and none was started. Do not send the end user to a verification link. Instead, complete the customer profile and send the identity document images with POST /customers/{customer_id}/documents; OZAV screens the customer and decides on its own. Checks that OZAV runs itself (aml_name_screening, economic_capacity) can still be requested here without the hosted ones.",
                        "param": "capabilities",
                        "details": {
                          "verification_mode": "reliance",
                          "hosted_capabilities": [
                            "kyc_document",
                            "liveness"
                          ]
                        },
                        "request_id": "5f0c2d1e-8a3b-4c7d-9e6f-1a2b3c4d5e6f"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/verifications \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"capabilities\": [\n      \"kyc_document\",\n      \"liveness\",\n      \"aml_name_screening\"\n    ],\n    \"redirect_url\": \"https://acme.example/kyc/return\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/verifications\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"capabilities\": [\n      \"kyc_document\",\n      \"liveness\",\n      \"aml_name_screening\"\n    ],\n    \"redirect_url\": \"https://acme.example/kyc/return\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/verifications\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"capabilities\": [\n            \"kyc_document\",\n            \"liveness\",\n            \"aml_name_screening\",\n        ],\n        \"redirect_url\": \"https://acme.example/kyc/return\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/verifications/{verification_id}": {
      "get": {
        "operationId": "getVerification",
        "tags": [
          "Verifications"
        ],
        "summary": "Retrieve a verification session",
        "description": "Retrieves a verification session by its `ozav_vrf_` id. Poll this after\n`POST /verifications` to watch each requested check move through its\n`CheckOutcome` and the session `status` reach `completed`.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "verifications:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "verification_id",
            "in": "path",
            "required": true,
            "description": "The opaque verification id (`ozav_vrf_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The verification session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                },
                "examples": {
                  "in_progress": {
                    "summary": "Checks running - poll until status is completed",
                    "value": {
                      "id": "ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "verification",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "in_progress",
                      "checks": {
                        "kyc_document": {
                          "outcome": "pass",
                          "verified_at": "2026-07-12T18:04:00Z"
                        },
                        "liveness": {
                          "outcome": "pending"
                        },
                        "aml_name_screening": {
                          "outcome": "pass",
                          "verified_at": "2026-07-12T18:04:30Z"
                        }
                      },
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/verifications/ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/verifications/ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/verifications/ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/verifications/{verification_id}/simulate": {
      "post": {
        "operationId": "simulateVerification",
        "tags": [
          "Verifications"
        ],
        "summary": "Simulate a sandbox verification result",
        "description": "Sandbox-only helper for test API keys. Marks a verification as approved\nor rejected and updates the linked customer's KYC status so downstream\nsandbox flows, including simulated wallet reads, BRL/Pix account reads,\nand card issuance, can be exercised without fake identity documents.\nSandbox wallet and account records are projections only; they do not\ncreate an on-chain address or open a real bank account.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "verifications:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "verification_id",
            "in": "path",
            "required": true,
            "description": "The opaque verification id (`ozav_vrf_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "outcome"
                ],
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 255
                  }
                }
              },
              "examples": {
                "approved": {
                  "value": {
                    "outcome": "approved"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated verification.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/verifications/ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1/simulate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"outcome\": \"approved\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/verifications/ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1/simulate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"outcome\": \"approved\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/verifications/ozav_vrf_01J8Z9K2M3N4P5Q6R7S8T9U0V1/simulate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"outcome\": \"approved\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/lists/entries": {
      "post": {
        "operationId": "createListEntry",
        "tags": [
          "Lists"
        ],
        "summary": "Add an entry to your block or allow list",
        "description": "Adds a value to your organization's own block or allow list. Your lists apply to your\nscreenings only: a block-list match escalates `suggested_decision` (`review`, or `decline`\nwith `action: decline`) and always wins over an allow match; an allow-list match suppresses\nonly contact signals - never a block-list, sanctions or PEP match. See `type` for which\nscreenings each value type can match.\n\nValues are stored as a keyed hash plus a masked hint - never in full. The same value cannot be\nactive twice: on the same list it returns 409 `LIST_ENTRY_EXISTS`, on the other list 409\n`LIST_ENTRY_CONFLICT`; both return the existing entry id in `error.details.existing_id`.\n\nUp to 10,000 active entries per organization and environment (a soft limit: concurrent\ncreations right at the limit may pass it by a few entries).\n\nA `name` entry matches every subject with that name: a `name` block with `action: decline`\ndeclines homonyms too. Prefer `document` or `email` for decline, and `review` for names.\n\nRequires the `lists:write` scope (`lists:read` to list). API keys created before these scopes\nexisted do not gain them: create a new key with the scopes you need.\n\n**Data protection.** Your organization is the controller of what it puts on its lists; OZAV\nprocesses it on your behalf. Deleting an entry stops it from matching at once and keeps a\nmarked record as the audit trail of your due diligence; a physical purge is done by OZAV on\nyour written request.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "lists:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListEntryCreateRequest"
              },
              "examples": {
                "block_document": {
                  "summary": "Block a tax id (matches screenings by customer_id)",
                  "value": {
                    "list": "block",
                    "type": "document",
                    "value": "12.345.678/0001-90",
                    "action": "decline"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListEntry"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/lists/entries \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"list\": \"block\",\n    \"type\": \"document\",\n    \"value\": \"12.345.678/0001-90\",\n    \"action\": \"decline\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/lists/entries\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"list\": \"block\",\n    \"type\": \"document\",\n    \"value\": \"12.345.678/0001-90\",\n    \"action\": \"decline\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/lists/entries\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"list\": \"block\",\n        \"type\": \"document\",\n        \"value\": \"12.345.678/0001-90\",\n        \"action\": \"decline\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listListEntries",
        "tags": [
          "Lists"
        ],
        "summary": "List your block and allow list entries",
        "description": "Lists your active entries, newest first, with the standard cursor pagination.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "lists:read"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "list",
            "in": "query",
            "description": "Filter to `block` or `allow`.",
            "schema": {
              "type": "string",
              "enum": [
                "block",
                "allow"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated entries.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ListEntry"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/lists/entries \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/lists/entries\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/lists/entries\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/lists/entries/{entry_id}": {
      "delete": {
        "operationId": "deleteListEntry",
        "tags": [
          "Lists"
        ],
        "summary": "Remove an entry from your list",
        "description": "Removes the entry from your screenings from now on. A marked record is kept as your audit trail; a physical purge is done by OZAV on your written request.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "lists:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "entry_id",
            "in": "path",
            "required": true,
            "description": "The entry id (`ozav_lst_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The entry was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListEntryDeleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/lists/entries/{entry_id} \\\n  -X DELETE \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/lists/entries/{entry_id}\", {\n  method: \"DELETE\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.delete(\n    \"https://developers.ozav.io/v2/lists/entries/{entry_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/screenings": {
      "post": {
        "operationId": "createScreening",
        "tags": [
          "Screenings"
        ],
        "summary": "Screen a person, business, customer, or wallet address against designated lists",
        "description": "Point-in-time screening against OZAV's in-house designated-persons\ndataset (UN, OFAC, EU, UK and national sources, refreshed daily).\nSubjects may be inline (person/business) or reference an existing\ncustomer.\n\nSend an `Idempotency-Key` to retry safely: the same key and body return the same\nscreening, with `Idempotent-Replayed: true`, and on OZAV Services the retry is not\ncharged again.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "screenings:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/X402PaymentSignature"
          },
          {
            "$ref": "#/components/parameters/XOzavSimulatePayment"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject"
                ],
                "properties": {
                  "subject": {
                    "$ref": "#/components/schemas/ScreeningSubject"
                  },
                  "lists": {
                    "type": "array",
                    "description": "Which designated lists to screen against. Each item is one of:\n- `sanctions` - consolidated sanctions / designated-persons lists\n  (UN, OFAC, EU, UK and national sources). Consulted for every subject.\n- `pep` - politically exposed persons. Consulted in-house for subjects\n  whose `country` is `BR` (the Brazilian PEP register, refreshed\n  monthly; `list_source` is `national`). For any other or missing\n  country the list cannot be consulted and the result is `review`\n  with an empty `matches` array.\n- `adverse_media` - negative-news screening. Not consultable yet: the\n  result is `review` with an empty `matches` array.\n\nDefaults to `[ sanctions ]` when omitted.\n",
                    "items": {
                      "type": "string",
                      "enum": [
                        "sanctions",
                        "pep",
                        "adverse_media"
                      ]
                    },
                    "default": [
                      "sanctions"
                    ]
                  }
                }
              },
              "examples": {
                "person": {
                  "summary": "Screen a person against sanctions + PEP",
                  "value": {
                    "subject": {
                      "person": {
                        "name": "Jane Example Doe",
                        "date_of_birth": "1980-04-12",
                        "country": "BR"
                      }
                    },
                    "lists": [
                      "sanctions",
                      "pep"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Screening result (synchronous when possible).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Screening"
                },
                "examples": {
                  "clear": {
                    "summary": "No match",
                    "value": {
                      "id": "ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "screening",
                      "subject": {
                        "person": {
                          "name": "Jane Example Doe",
                          "date_of_birth": "1980-04-12",
                          "country": "BR"
                        }
                      },
                      "lists": [
                        "sanctions",
                        "pep"
                      ],
                      "status": "completed",
                      "result": "clear",
                      "matches": [],
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  },
                  "match_found": {
                    "summary": "A match - read list_source, matched_name, and strength",
                    "value": {
                      "id": "ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "screening",
                      "subject": {
                        "person": {
                          "name": "Jane Example Doe",
                          "date_of_birth": "1980-04-12",
                          "country": "BR"
                        }
                      },
                      "lists": [
                        "sanctions",
                        "pep"
                      ],
                      "status": "completed",
                      "result": "match_found",
                      "matches": [
                        {
                          "list_source": "ofac",
                          "matched_name": "JANE E. DOE",
                          "strength": "alias",
                          "list_entry_ref": "SDN-12345"
                        }
                      ],
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/screenings \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"subject\": {\n      \"person\": {\n        \"name\": \"Jane Example Doe\",\n        \"date_of_birth\": \"1980-04-12\",\n        \"country\": \"BR\"\n      }\n    },\n    \"lists\": [\n      \"sanctions\",\n      \"pep\"\n    ]\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/screenings\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"subject\": {\n      \"person\": {\n        \"name\": \"Jane Example Doe\",\n        \"date_of_birth\": \"1980-04-12\",\n        \"country\": \"BR\"\n      }\n    },\n    \"lists\": [\n      \"sanctions\",\n      \"pep\"\n    ]\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/screenings\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"subject\": {\n            \"person\": {\n                \"name\": \"Jane Example Doe\",\n                \"date_of_birth\": \"1980-04-12\",\n                \"country\": \"BR\",\n            },\n        },\n        \"lists\": [\n            \"sanctions\",\n            \"pep\",\n        ],\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listScreenings",
        "tags": [
          "Screenings"
        ],
        "summary": "List screenings",
        "description": "Lists screenings, newest first, with the standard cursor pagination.\nFilter to one subject with the `customer_id` query parameter to\nreconstruct a customer's screening trail for audit. Returns a `Page` of\n`Screening`.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "screenings:read"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated screenings.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Screening"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of screenings",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "screening",
                          "subject": {
                            "person": {
                              "name": "Jane Example Doe",
                              "date_of_birth": "1980-04-12",
                              "country": "BR"
                            }
                          },
                          "lists": [
                            "sanctions",
                            "pep"
                          ],
                          "status": "completed",
                          "result": "clear",
                          "matches": [],
                          "created_at": "2026-07-12T18:00:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/screenings \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/screenings\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/screenings\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/screenings/{screening_id}": {
      "get": {
        "operationId": "getScreening",
        "tags": [
          "Screenings"
        ],
        "summary": "Retrieve a screening",
        "description": "Retrieves a single screening by its `ozav_scr_` id, including its\n`result` and any `matches`. Retain the id as evidence of the check.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "screenings:read"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "screening_id",
            "in": "path",
            "required": true,
            "description": "The opaque screening id (`ozav_scr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The screening.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Screening"
                },
                "examples": {
                  "match_found": {
                    "summary": "A match - read list_source, matched_name, and strength",
                    "value": {
                      "id": "ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "screening",
                      "subject": {
                        "person": {
                          "name": "Jane Example Doe",
                          "date_of_birth": "1980-04-12",
                          "country": "BR"
                        }
                      },
                      "lists": [
                        "sanctions"
                      ],
                      "status": "completed",
                      "result": "match_found",
                      "matches": [
                        {
                          "list_source": "ofac",
                          "matched_name": "JANE E. DOE",
                          "strength": "alias",
                          "list_entry_ref": "SDN-12345"
                        }
                      ],
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/screenings/ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/screenings/ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/screenings/ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/screenings/{screening_id}/hits/{hit_id}": {
      "patch": {
        "operationId": "decideScreeningHit",
        "tags": [
          "Screenings"
        ],
        "summary": "Decide a screening hit",
        "description": "Marks one hit of a screening as `false_positive` or `confirmed`, with a reason.\n\n**Decide once.** The decision is kept by the hit's fingerprint (list entry, strength, listed name)\nand reused by the following screenings of the same subject that hit the same entry - when the\nsubject is identifiable: a `customer_id`, or a person with `date_of_birth`. Without that (a\nperson without date of birth, or an inline business), the decision applies to this screening\nonly, because a bare name matches every homonym. A changed list entry starts `open`.\n\nThe screening you decide on keeps the `suggested_decision` it was created with; its\n`matches[].status` shows the decision. Your decision is the final one, on any hit, sanctions\nlists included. When OZAV compliance has also reviewed a hit, its decision shows until you\ndecide. Screenings created before hit decisions existed return 422 `HIT_NOT_DECIDABLE`: screen\nthe subject again and decide on the new screening.\nSending the decision the hit already has (by you) returns 200 with the current state and\nrecords nothing new.\n",
        "x-ozav-entitlement": "compliance",
        "x-ozav-scopes": [
          "screenings:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "screening_id",
            "in": "path",
            "required": true,
            "description": "The opaque screening id (`ozav_scr_...`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hit_id",
            "in": "path",
            "required": true,
            "description": "The hit id (`hit_...`) from `matches[].id`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScreeningHitDecisionRequest"
              },
              "examples": {
                "false_positive": {
                  "summary": "A PEP hit that is a homonym",
                  "value": {
                    "decision": "false_positive",
                    "reason": "Different date of birth and tax id checked against the register."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The hit with its effective decision.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScreeningHit"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/screenings/ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/hits/{hit_id} \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"decision\": \"false_positive\",\n    \"reason\": \"Different date of birth and tax id checked against the register.\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/screenings/ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/hits/{hit_id}\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"decision\": \"false_positive\",\n    \"reason\": \"Different date of birth and tax id checked against the register.\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/screenings/ozav_scr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/hits/{hit_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"decision\": \"false_positive\",\n        \"reason\": \"Different date of birth and tax id checked against the register.\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/quotes": {
      "post": {
        "operationId": "createQuote",
        "tags": [
          "Quotes"
        ],
        "summary": "Create an FX quote",
        "description": "Quotes are opaque and indicative (`floating`: the rate re-derives at\nexecution), and carry the OZAV all-in client-facing rate. When enabled, they also carry the additional client-selected\nspread in basis points. A quote is executed by creating a transfer that\nreferences it.\n\n**Setting your own spread (revenue share).** Pass the optional\n`spread_bps` to add basis points on top of the OZAV all-in rate for\nthis quote. This value is your additional client-selected spread; it\nrides the quote through to settlement and is echoed back as\n`exchange_conditions.spread_bps`. OZAV cost, floor, margin, and split\nanalytics are internal and are never exposed in the public response.\nAvailability of client-selected spread is gated on enablement for your\norganization.\n\n**Sandbox.** With a test key (`ozav_sk_test_`), quotes are simulated.\nSandbox quotes use the current production reference for the currency\npair plus a 1 bp sandbox margin and your configured spread, the way\nproduction prices them. When no current reference exists for a pair,\na fixed table stands in. Sandbox quotes are not executable.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "quotes:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "source",
                  "target"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "The opaque customer id (ozav_cus_...). Resolved scoped to the caller; an unknown or cross-tenant id returns 404."
                  },
                  "source": {
                    "type": "object",
                    "required": [
                      "currency"
                    ],
                    "properties": {
                      "currency": {
                        "$ref": "#/components/schemas/Currency"
                      },
                      "amount": {
                        "$ref": "#/components/schemas/DecimalAmount"
                      },
                      "chain": {
                        "type": "string",
                        "description": "**Send this when the source is a stablecoin.** The network the\nfunds will leave from (`tron`, `ethereum`, `polygon`, `solana`,\nand so on), the same value you send as `source.chain` on `POST /transfers`.\nThe network cost of moving the stablecoin is part of the price,\nand it differs by orders of magnitude between networks, so a\nquote for a stablecoin source needs to know the network. A quote\nthat cannot determine it is refused with `422\nNETWORK_COST_UNPRICED`; a transfer whose `source.chain` differs\nfrom the one the quote priced is refused with `422\nQUOTE_NETWORK_MISMATCH`. Ignored for fiat sources.\n"
                      }
                    }
                  },
                  "target": {
                    "type": "object",
                    "required": [
                      "currency"
                    ],
                    "properties": {
                      "currency": {
                        "$ref": "#/components/schemas/Currency"
                      },
                      "amount": {
                        "$ref": "#/components/schemas/DecimalAmount"
                      },
                      "chain": {
                        "type": "string",
                        "description": "**Send this when the target is a stablecoin.** The network the\nstablecoin will be delivered on (`tron`, `ethereum`, `polygon`,\n`solana`, and so on).\n\nDelivering a stablecoin is an on-chain transfer, and its network\ncost differs by orders of magnitude: the same delivery costs about\nUS$ 3.39 per leg on `tron` and about US$ 0.001 on `solana` or\n`base`. Sending this lets the quote price that cost instead of\nguessing it.\n\nYour account's configured network is used when you omit it. Where\nneither is available the quote is still returned, without the\nnetwork cost priced in - but the network you actually deliver on\nis the one that decides what the operation costs, so send it.\nIgnored for fiat targets.\n"
                      }
                    }
                  },
                  "source_rail": {
                    "$ref": "#/components/schemas/RequestRail"
                  },
                  "destination_rail": {
                    "$ref": "#/components/schemas/RequestRail"
                  },
                  "destination_country": {
                    "type": "string",
                    "pattern": "^[A-Za-z]{2}$",
                    "example": "CN",
                    "description": "Optional. ISO 3166-1 alpha-2 country of the beneficiary's bank, for a\nfiat target. Some currencies cost more to deliver in one country than\nanother (USD paid in China, say), and the quote prices the country you\nsend. Without it, the quote uses the currency's default country. A\ncurrency priced only for other countries answers `422\nCORRIDOR_NOT_PRICED_FOR_COUNTRY` (`param: destination_country`). It\ncannot be combined with `via` yet (`400\nDESTINATION_COUNTRY_NOT_SUPPORTED_WITH_VIA`).\n"
                  },
                  "spread_bps": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000,
                    "description": "Optional. Additional client-selected spread for this quote,\nin integer basis points. **This is the spread you charge your\nend customer, not an OZAV charge; it is revenue-shared with\nOZAV under your commercial agreement.** Available only when\nenabled for your organization. A non-integer, negative, or\nout-of-range value is rejected with 400.\n\n\n**It is applied inside `rate`.** The quote you receive is\nalready marked up by this many basis points, so comparing\nthat rate to a market reference (an exchange spot price, say)\nwill look worse by about this amount. It is echoed back as\n`exchange_conditions.spread_bps`, and\n`exchange_conditions.rate_without_spread` returns the rate\nbefore it.\n\n\n**Omitting it applies your configured default**, which can be\ndifferent from `0` if one was negotiated for your\norganization. Send `0` explicitly to quote at the OZAV all-in\nrate (all-in except for any `amount_deducted`).\n"
                  },
                  "funding_source": {
                    "type": "string",
                    "enum": [
                      "incoming_transfer",
                      "named_account_balance"
                    ],
                    "description": "Optional. How the source fiat is funded. `incoming_transfer`\nincludes the measured inbound cost (USD SWIFT US$ 35;\nEUR SEPA / GBP Faster Payments US$ 5, deducted from the\ncredit). `named_account_balance` converts funds already on\nthe named account and includes the outbound cost to the\ndesk (USD wire US$ 35 + 12 bps; EUR/GBP local US$ 5 + 12\nbps, charged on top). Omitted behaves as\n`incoming_transfer` for USD, EUR and GBP.\n"
                  },
                  "facility_preview": {
                    "type": "boolean",
                    "description": "Ask for the **FX rate lock conditions** alongside the quote. When `true` and your organization has the facility contracted, the response carries an extra `facility` block; `POST /transfers` then accepts its `terms_version` as `facility_terms_version`. This is the only place that value comes from.\n\n**Silent when unavailable.** A client without the facility gets a normal `201` quote with no `facility` block and no error - the preview never fails a quote, because the quote is the product. Read the absence of the block as \"not available for this account\", and use `POST /transfers` (which refuses loudly with `422 FACILITY_LOCK_UNAVAILABLE` and a `details.reason`) when you need to know why.\n\n**It prices, it does not reserve.** `capacity_guaranteed` is always `false`: room under the shared ceiling is resolved at acceptance, inside the lock. A preview that promised capacity would be a promise the design cannot honour."
                  },
                  "via": {
                    "type": "object",
                    "required": [
                      "currency"
                    ],
                    "additionalProperties": false,
                    "properties": {
                      "currency": {
                        "type": "string",
                        "description": "The stablecoin the conversion passes through (`USDT` or `USDC`)."
                      },
                      "chain": {
                        "type": "string",
                        "description": "Optional. The network the stablecoin moves on. Omit it and OZAV uses the network the payout route runs on. A network the route does not serve is refused with `422 ASSET_NOT_AVAILABLE_ON_NETWORK`."
                      }
                    },
                    "description": "Optional. **Quote BRL into a foreign currency in a single quote**, passing\nthrough a stablecoin: OZAV buys the stablecoin with the BRL and pays\nthe foreign currency out of it. The response carries the end-to-end\n`rate`, plus `via` and `legs`. Each leg is priced on its own, and the\nsecond leg is priced on exactly the stablecoin the first delivers.\n\n- The source must be `BRL` and the target a foreign payout currency\n  (USD, EUR, GBP, ...). Send `source.amount`: `target.amount` is\n  refused with `400 SOURCE_AMOUNT_REQUIRED`.\n- Live keys: enabled per organization, otherwise `422 VIA_NOT_ENABLED`\n  (`param: via`). Once enabled, every payout currency in the\n  `POST /payout-destinations` corridor table is open. Test keys quote it freely against the\n  sandbox simulator; while the simulator is unavailable they get `422\n  RAIL_NOT_AVAILABLE` (`param: via`).\n- `spread_bps` applies once, on the first leg. The top-level\n  `exchange_conditions` echoes it as `spread_bps` and carries no\n  `rate_without_spread` (an end-to-end rate has no derivable\n  \"without spread\"); each leg carries its own\n  `exchange_conditions`.\n- The payout route caps each transfer: below its minimum you get\n  `400 AMOUNT_BELOW_MINIMUM`, above its maximum `400\n  AMOUNT_ABOVE_MAXIMUM` (split the operation).\n- The network is `via.chain`. Not combinable with `source.chain`,\n  `target.chain`, `facility_preview` or `funding_source` (`400\n  FIELD_NOT_ALLOWED`, `param` names the field).\n- Floating, like every quote: each leg re-prices at execution.\n\nRefusals specific to `via`:\n- `400 UNSUPPORTED_PAIR`: the source is not `BRL` (`param: source.currency`)\n  or the target is not a foreign payout currency (`param: target.currency`).\n- `400 UNSUPPORTED_CURRENCY` (`param: via.currency`) and\n  `400 SOURCE_CHAIN_UNSUPPORTED` (`param: via.chain`).\n- `422 RAIL_NOT_AVAILABLE`: no payout route covers the currency through\n  that stablecoin right now (`param: target.currency`), or the requested\n  `destination_rail` is not served by it (`param: destination_rail`).\n- `422 RAIL_NOT_AVAILABLE` (`param: via`): the network cost of the route\n  cannot be priced right now.\n- `422 ASSET_NOT_AVAILABLE_ON_NETWORK` (`param: via.chain`).\n- `422 TICKET_BELOW_COST` (`param: source.amount`): the amount does\n  not cover the route's costs, network cost included; increase\n  `source.amount`.\n- `422 NO_QUOTE_AVAILABLE`: the two legs did not compose; request a new quote.\n\n**Executing it.** Send the quote to `POST /transfers` with `source` a Pix\npayment (`fiat_payment`, `BRL`, `rail: pix`) and `destination` a payout\ndestination in the target currency. The response carries the Pix to pay.\nWhen the Pix settles, OZAV buys the stablecoin into the customer's OZAV\nwallet and pays the target currency out on its own; the transfer stays\n`converting` until the payout ends.\n- The payout leg re-prices when the Pix settles, so the amount delivered\n  can differ from the quote's `target.amount`.\n- Everything the payout can refuse (destination, documents, minimum,\n  maximum) is checked before the Pix is created, with the same codes as a\n  direct payout.\n- The customer needs an OZAV wallet that holds the stablecoin: `422\n  WALLET_REQUIRED` (`param: customer_id`).\n- If the payout is refused after the Pix settles, the transfer moves to\n  `needs_review` (event `transfer.updated`, not terminal) and the\n  stablecoin stays in the customer's OZAV wallet until OZAV resolves it.\n- If the payout starts and then fails or is cancelled, the transfer ends\n  `failed` or `cancelled` with `source_leg: completed` (the Pix was\n  received). After `cancelled` the stablecoin is back in the customer's\n  OZAV wallet; after `failed` it stays held until OZAV resolves it.\n- `target.amount` stays the quoted estimate; the transfer does not report\n  the re-priced amount delivered.\n- Not available on test keys, nor with `facility_terms_accepted`: `422\n  NOT_AVAILABLE`. A `via` quote the API can no longer execute: `422\n  QUOTE_EXPIRED`, request a new one.\n"
                  }
                },
                "description": "Provide exactly one of source.amount or target.amount."
              },
              "examples": {
                "brl_to_usd": {
                  "summary": "BRL → USD at the OZAV all-in rate (start here)",
                  "description": "`spread_bps: 0`, sent explicitly: leaving it out applies your\naccount's default spread, if one is configured. The `rate` you\nget back is the OZAV all-in rate and\n`exchange_conditions.rate_without_spread` equals it. Compare\n`rate_without_spread` against a market reference.\n",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "source": {
                      "currency": "BRL",
                      "amount": "50000.00"
                    },
                    "target": {
                      "currency": "USD"
                    },
                    "spread_bps": 0
                  }
                },
                "brl_to_usd_with_revenue_share": {
                  "summary": "BRL → USD adding a 40 bps spread you charge your end customer",
                  "description": "**`spread_bps` is what you charge your end customer**, not\nan OZAV charge (it is revenue-shared with OZAV under your\ncommercial agreement). It is applied inside `rate`. The quote\nyou receive is already marked up by it, and comparing that rate\nto a market reference will look worse by about this many basis\npoints; `exchange_conditions.rate_without_spread` shows the\nrate before it.\n\nCopy this example only if you mean to add your own spread. To\nquote at the OZAV rate, use the example above.\n",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "source": {
                      "currency": "BRL",
                      "amount": "50000.00"
                    },
                    "target": {
                      "currency": "USD"
                    },
                    "spread_bps": 40
                  }
                },
                "brl_to_usd_via_usdt": {
                  "summary": "BRL → USD through USDT, one end-to-end quote",
                  "description": "One quote for the whole route: BRL buys USDT, and the USDT pays out\nUSD. The response carries the end-to-end `rate` plus `via` and the two\n`legs`. Live keys need the route enabled for the organization.\n",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "source": {
                      "currency": "BRL",
                      "amount": "100000.00"
                    },
                    "target": {
                      "currency": "USD"
                    },
                    "via": {
                      "currency": "USDT"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "examples": {
                  "quote": {
                    "summary": "OZAV all-in quote (no client spread)",
                    "value": {
                      "id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "quote",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "rate_type": "floating",
                      "rate": "0.1881",
                      "source": {
                        "amount": "50000.00",
                        "currency": "BRL"
                      },
                      "target": {
                        "amount": "9405.00",
                        "currency": "USD"
                      },
                      "exchange_conditions": {
                        "spread_bps": 0,
                        "rate_without_spread": "0.1881"
                      },
                      "expires_at": null,
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  },
                  "quote_via_usdt": {
                    "summary": "BRL → USD via USDT: one end-to-end quote with its two legs",
                    "value": {
                      "id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V2",
                      "object": "quote",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "rate_type": "floating",
                      "rate": "0.19074130",
                      "source": {
                        "amount": "100000.00",
                        "currency": "BRL"
                      },
                      "target": {
                        "amount": "19074.13",
                        "currency": "USD"
                      },
                      "exchange_conditions": {
                        "spread_bps": 0
                      },
                      "expires_at": null,
                      "created_at": "2026-10-05T12:00:00Z",
                      "via": {
                        "currency": "USDT",
                        "chain": "ethereum"
                      },
                      "legs": [
                        {
                          "source": {
                            "amount": "100000.00",
                            "currency": "BRL"
                          },
                          "target": {
                            "amount": "19106.618234",
                            "currency": "USDT"
                          },
                          "rate": "0.19106618",
                          "exchange_conditions": {
                            "spread_bps": 0,
                            "rate_without_spread": "0.19106618"
                          }
                        },
                        {
                          "source": {
                            "amount": "19106.610000",
                            "currency": "USDT"
                          },
                          "target": {
                            "amount": "19074.13",
                            "currency": "USD"
                          },
                          "rate": "0.9983",
                          "exchange_conditions": {
                            "spread_bps": 0,
                            "rate_without_spread": "0.9983"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/quotes \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"source\": {\n      \"currency\": \"BRL\",\n      \"amount\": \"50000.00\"\n    },\n    \"target\": {\n      \"currency\": \"USD\"\n    },\n    \"spread_bps\": 0\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/quotes\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"source\": {\n      \"currency\": \"BRL\",\n      \"amount\": \"50000.00\"\n    },\n    \"target\": {\n      \"currency\": \"USD\"\n    },\n    \"spread_bps\": 0\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/quotes\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"source\": {\n            \"currency\": \"BRL\",\n            \"amount\": \"50000.00\",\n        },\n        \"target\": {\n            \"currency\": \"USD\",\n        },\n        \"spread_bps\": 0,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/quotes/{quote_id}": {
      "get": {
        "operationId": "getQuote",
        "tags": [
          "Quotes"
        ],
        "summary": "Retrieve a quote",
        "description": "Retrieves a quote by its `ozav_q_` id, including its `rate`,\n`exchange_conditions`, and `expires_at`. Every quote today is\n`rate_type: floating` with `expires_at: null`: the rate is indicative\nand re-derives at execution (see `Quote.rate_type`).\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "quotes:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "quote_id",
            "in": "path",
            "required": true,
            "description": "The opaque quote id (`ozav_q_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "examples": {
                  "quote": {
                    "summary": "A BRL → USD quote",
                    "value": {
                      "id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "quote",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "rate_type": "floating",
                      "rate": "0.1881",
                      "source": {
                        "amount": "50000.00",
                        "currency": "BRL"
                      },
                      "target": {
                        "amount": "9405.00",
                        "currency": "USD"
                      },
                      "exchange_conditions": {
                        "spread_bps": 0,
                        "rate_without_spread": "0.1881"
                      },
                      "expires_at": null,
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  },
                  "quote_via_usdt": {
                    "summary": "A quote requested via USDT: the same `via` and `legs` as when it was created",
                    "value": {
                      "id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V2",
                      "object": "quote",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "rate_type": "floating",
                      "rate": "0.19074130",
                      "source": {
                        "amount": "100000.00",
                        "currency": "BRL"
                      },
                      "target": {
                        "amount": "19074.13",
                        "currency": "USD"
                      },
                      "exchange_conditions": {
                        "spread_bps": 0
                      },
                      "expires_at": null,
                      "created_at": "2026-10-05T12:00:00Z",
                      "via": {
                        "currency": "USDT",
                        "chain": "ethereum"
                      },
                      "legs": [
                        {
                          "source": {
                            "amount": "100000.00",
                            "currency": "BRL"
                          },
                          "target": {
                            "amount": "19106.618234",
                            "currency": "USDT"
                          },
                          "rate": "0.19106618",
                          "exchange_conditions": {
                            "spread_bps": 0,
                            "rate_without_spread": "0.19106618"
                          }
                        },
                        {
                          "source": {
                            "amount": "19106.610000",
                            "currency": "USDT"
                          },
                          "target": {
                            "amount": "19074.13",
                            "currency": "USD"
                          },
                          "rate": "0.9983",
                          "exchange_conditions": {
                            "spread_bps": 0,
                            "rate_without_spread": "0.9983"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/quotes/ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/quotes/ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/quotes/ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/transactions": {
      "get": {
        "operationId": "listTransactions",
        "tags": [
          "Transactions"
        ],
        "summary": "List every movement in one timeline",
        "description": "One feed for everything that moved value for your customers: the\ntransfers you created through this API, and the on-chain deposits\ncredited into custody. You no longer have to stitch two listings together\nand guess the order between them.\n\nOrdered newest first. Each row carries a `type` (`transfer` or\n`deposit`), so you can branch without a second lookup, and the `id` is\nthe id of the underlying resource - use it to fetch the full record.\n\nPaginated with the standard opaque cursor. The cursor spans both sources\nat once: it is not one cursor per source, so a busy day on one side can\nnever hide rows from the other.\n\n**Card transactions are not in this feed.** They are read per card at\n`GET /cards/{card_id}/transactions`.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id. Applies to\nboth sources: transfers created for that customer, and the deposits\ncredited to it.\n\nAn id that does not exist, or belongs to another organization or\nanother environment, returns an empty page rather than `404` - the\nsame behaviour as `GET /transfers`, so a filter never tells you\nwhether an id exists somewhere you cannot see.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of movements, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transaction"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "examples": {
                  "mixed": {
                    "summary": "A transfer and an on-chain deposit in one timeline",
                    "value": {
                      "data": [
                        {
                          "object": "transaction",
                          "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "type": "transfer",
                          "status": "completed",
                          "amount": "50000.00",
                          "currency": "BRL",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "created_at": "2026-07-12T18:01:00Z"
                        },
                        {
                          "object": "transaction",
                          "id": "6f499bc1-bf2a-49ef-a1f2-86ec5acc48d1",
                          "type": "deposit",
                          "status": "credited",
                          "amount": "250.000000",
                          "currency": "USDC",
                          "network": "base",
                          "created_at": "2026-07-11T09:20:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transactions \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transactions\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/transactions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/transfers": {
      "post": {
        "operationId": "createTransfer",
        "tags": [
          "Transfers"
        ],
        "summary": "Create a transfer",
        "description": "The single money-movement resource. The pair (`source.type`,\n`destination.type`) selects the flow - you never call a different\nendpoint for on-ramp vs off-ramp:\n\n| source          | destination          | flow            | availability |\n|-----------------|----------------------|-----------------|--------------|\n| `fiat_payment`  | `payout_destination` | FX (fiat→fiat)  | live         |\n| `fiat_payment`  | `wallet`             | on-ramp         | live         |\n| `account`       | `wallet`             | on-ramp from the named account's balance | live |\n| `wallet`        | `payout_destination` | off-ramp        | live         |\n| `wallet`        | `account`            | off-ramp to the customer's own account | live |\n| `wallet`        | `wallet`             | wallet transfer | live         |\n| `wallet`        | `crypto_address`     | crypto send     | live         |\n\n**Sending to an on-chain address runs on this route.** A `wallet` source\nwith a `crypto_address` destination is a custody move: OZAV signs from the\nsource wallet's vault and broadcasts. No quote is involved (it is a\nsame-asset move, not an exchange), so send `amount` and omit `quote_id`.\nIt needs the `custody` or the `liquidity` product. The `/wallets` routes need `custody`:\nlisting wallets (`GET /wallets`), reading a customer's wallet, its deposit addresses,\naddresses and policy, and `POST /wallets/{wallet_id}/transfers`. It is subject to the two controls below.\n\n**On-ramp from the account balance.** An `account` source spends the BRL balance of the\ncustomer's BRL named account: Pix payments to the account's key accumulate there, and nothing\nconverts by itself. An account that cannot fund an order yet gets `422 ACCOUNT_NOT_READY`:\nit is not active, its institution cannot send Pix, or its balance could not be read just now.\nConverting more than the balance not already committed to an open order gets\n`422 ACCOUNT_BALANCE_INSUFFICIENT`. If OZAV pauses this flow, every organization gets\n`422 ACCOUNT_FUNDED_ORDERS_NOT_ENABLED` at once: nothing in your request causes it.\n\nSome of these refusals come after the transfer is recorded: it ends `failed`, and a retry\nwith the same `Idempotency-Key` returns the same refusal. To try again later, send the\nrequest with a new key. An `ACCOUNT_NOT_READY` that persists across a few retries means the\naccount cannot fund orders yet. Each conversion is one transfer with a quote, like any on-ramp, and the\nBRL 1,000 on-ramp floor applies per transfer, not per deposit. A smaller deposit stays in\nthe account and adds up with the next ones.\n\nIn the sandbox, a test key converts from a sandbox account: credit it first with\n`POST /accounts/{account_id}/simulate/credit`, then send the transfer. The order runs on the\nsimulator and the conversion debits the simulated balance.\n\n**Wallet-to-wallet runs here too.** The source wallet must belong to the\n`customer_id` on the request - that is whose money moves. The destination\nonly has to be a wallet in your organization, so you can move value between\ntwo of your customers' wallets. A wallet outside your organization is not\nfound, and a deactivated wallet does not receive.\n\nThe two controls:\n\n- **Managed wallets only.** A self-custody source is refused with\n  `422 WALLET_NOT_MANAGED` - OZAV holds no key to sign with. That send\n  goes through `POST /wallets/{wallet_id}/transaction-intents`, which\n  returns unsigned calldata for the end-user's own key.\n- **Travel Rule (BCB Resolution 520)** on any send that leaves custody.\n  A `crypto_address` destination without complete Travel Rule data is\n  refused with `422 TRAVEL_RULE_REQUIRED`, and `details.missing_fields`\n  names each field that is missing. A destination that is an `ozav_wal_`\n  id is exempt: the move stays inside custody and both sides are already\n  verified.\n\n`POST /wallets/{wallet_id}/transfers` also carries a destination `memo`,\nwhich networks like Stellar need for exchange deposits (an exchange\ncredits by memo). `POST /transfers` has no `memo` field, so a memo-bearing\nsend belongs on the wallets surface even after custody lands here.\n\n**Receiving crypto needs nothing from you, and is not a transfer.** Fund an\naddress from `GET /wallets/{wallet_id}/deposit-addresses` and the credit\nlands on the wallet by itself. You see it in two places already:\n\n- `GET /wallets/{wallet_id}/balances` reflects the new amount;\n- `GET /transactions` lists the arrival alongside your transfers, in one\n  timeline, so you do not have to stitch two listings together.\n\nCreating a transfer with a `crypto_payment` source is refused with\n`422 NOT_AVAILABLE`, and that refusal is permanent rather than pending: an\narrival is observed, never requested, so there is nothing for you to create.\nThe one thing still missing is a *push* notification for it - today you\nlearn about an arrival by reading, not by being told.\n\nBRL/Pix on-ramp returns transaction-specific Pix payment\ninstructions on `source.payment_instructions`. BRL/Pix off-ramp\ncreates the operation only after the customer's stable\nfunds are reserved; final Pix-out remains gated until settlement rails\nare enabled. Other combinations fail closed (`422 RAIL_NOT_AVAILABLE` /\n`NOT_AVAILABLE`) until settlement is enabled - never a silent partial\nresult. See\n`x-ozav-transfer-flow-availability` for the machine-readable status.\n\n`quote_id` is required whenever source and target currencies differ, and\nis authoritative for the amount when present. For a same-currency move (no\nFX, so no quote) provide `amount` - it is how much to move. Exactly one of\n`quote_id` or `amount` must be present; supplying both is a 400. The\n`Idempotency-Key` header is required.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-transfer-flow-availability": {
          "fiat_payment__wallet": "live",
          "fiat_payment__payout_destination": "live",
          "wallet__payout_destination": "live",
          "wallet__wallet": "live",
          "wallet__crypto_address": "live"
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "source",
                  "destination"
                ],
                "description": "Provide `amount` when `quote_id` is omitted (same-currency moves carry the amount here); when `quote_id` is present the quote is authoritative and `amount` is rejected. Exactly one of the two is required.\n",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "The `ozav_cus_` id of the customer this transfer is for."
                  },
                  "quote_id": {
                    "type": "string",
                    "description": "The `ozav_q_` id of the quote to execute. Required when source and target currencies differ; authoritative for the amount when present."
                  },
                  "amount": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/TransferMonetaryAmount"
                      }
                    ],
                    "description": "Amount to move, for the no-quote same-currency path. Required when `quote_id` is absent; rejected when present.\n"
                  },
                  "source": {
                    "$ref": "#/components/schemas/TransferSource"
                  },
                  "destination": {
                    "$ref": "#/components/schemas/TransferDestination"
                  },
                  "travel_rule": {
                    "$ref": "#/components/schemas/TravelRule"
                  },
                  "metadata": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Metadata"
                      }
                    ],
                    "description": "Free-form string map, with **three reserved keys that some payout corridors require**. When a corridor requires one and it is absent, the transfer is refused at creation with a `400` that names the field and lists the accepted values - never later, with the funds already committed.\n\n`purpose_code` - what the payment is for. Required by the US, EU/UK, China, Japan, Peru and Nigeria corridors, each with its own list (e.g. China accepts only `GDDS` and `SCVE`; Peru accepts `FAMI`, `SUPP`, `COMP`, `INVS`, `OTHR`). The refusal carries the list for the corridor you are paying into.\n\n`source_of_cash` - where the money being sent comes from. Required by the Japan corridor. Closed list: `RETA` (retained earnings), `COMP` (compensation), `SVGS` (savings), `REST` (real estate sale), `SEAQ` (sale of equity), `CASH` (cash). This is a declaration about the sender's funds - OZAV never infers it.\n\n`payment_memo` - the reference that travels with the payment, by which the beneficiary recognises the credit. Required by the Japan corridor. When absent, OZAV sends a reference derived from the transfer.\n\nEvery other key is carried through untouched.\n"
                  },
                  "facility_terms_accepted": {
                    "type": "boolean",
                    "description": "**The FX rate lock (`trava de câmbio`), on this transfer.** Send `true`, together with `facility_terms_version`, to lock the rate at acceptance instead of converting at the price of the moment the money actually moves.\n\nBoth fields are optional and omitting them is the normal path - a transfer without them behaves exactly as before and the response carries no `facility` block.\n\n**Only BRL/Pix flows honour the lock.** The fields validate on every combination, but on a `crypto_address` destination, a wallet-to-wallet move, or the BRS redemption corridor they are accepted and have no effect - no lock is taken and no `facility` block is returned. Always branch on the presence of `facility` in the response rather than assuming the request implied a lock. The lock is a contracted facility: your organization needs a signed schedule (`Termo`) and a funded reserve before it is available, and without them the request is refused with `422 FACILITY_LOCK_UNAVAILABLE`.\n\n**Strictly `true`, not truthy.** `\"true\"`, `1` and `{}` all read as \"no lock requested\", silently - the transfer is created unlocked.\n\n**Get the version from the preview first:** `POST /quotes` with `facility_preview: true` returns a `facility` block whose `terms_version` is the only value this route accepts. Accepting without a version is refused with `422 FACILITY_TERMS_VERSION_REQUIRED`, because an acceptance that does not say what was accepted cannot support a charge later.\n\n**Where the lock actually engages differs by direction, and the off-ramp case is not obvious.** On a BRL/Pix on-ramp it engages at acceptance. On a BRL/Pix off-ramp it engages only when the customer's custody balance is insufficient - with enough balance the withdrawal takes the ordinary path and the lock does not apply. That outcome is reported in the response's `facility` block rather than passed over in silence."
                  },
                  "facility_terms_version": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The `terms_version` returned by the facility preview (`POST /quotes` with `facility_preview: true`). Required whenever `facility_terms_accepted` is `true`; rejected as stale when it does not match the schedule currently in force for your organization (`422 FACILITY_LOCK_UNAVAILABLE` with `details.reason = terms_version_mismatch` on the on-ramp, `409 PREFUNDING_TERMS_VERSION_STALE` on the off-ramp). Re-read the preview and accept again."
                  }
                }
              },
              "examples": {
                "brl_pix_onramp": {
                  "summary": "BRL / Pix on-ramp into a stablecoin wallet",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "quote_id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "source": {
                      "type": "fiat_payment",
                      "currency": "BRL",
                      "rail": "pix"
                    },
                    "destination": {
                      "type": "wallet",
                      "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "chain": "solana"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Degraded success - the transfer was dispatched, but finalizing the record afterwards failed (projection persist, owner binding, or serialization). The payment may already be on its way, so nothing is rolled back: the transfer is downgraded to `needs_review` and this reduced body is what you get. It is not the full Transfer resource - `source`, `destination`, `amounts` and `quote_id` are absent, and a client that assumes the `201` shape here will read `undefined` on the one response that means \"money may have moved\".\n\nBranch on the status code, not on `res.ok`. On a `200`, treat the transfer as unknown and reconcile with `GET /transfers/{id}` - do not retry the create, and do not present it to the payer as failed. A retry with the same `Idempotency-Key` replays this same body.\n",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "object",
                    "status"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The transfer id - use it to reconcile."
                    },
                    "object": {
                      "type": "string",
                      "const": "transfer"
                    },
                    "status": {
                      "type": "string",
                      "const": "needs_review"
                    }
                  }
                },
                "examples": {
                  "needs_review": {
                    "summary": "Dispatched, but the record could not be finalized - reconcile with GET",
                    "value": {
                      "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "transfer",
                      "status": "needs_review"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Transfer created.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                },
                "examples": {
                  "created": {
                    "summary": "Transfer created - poll status; source_leg/destination_leg settle independently",
                    "value": {
                      "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "transfer",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "quote_id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "awaiting_payment",
                      "source": {
                        "type": "fiat_payment",
                        "currency": "BRL",
                        "rail": "pix",
                        "payment_instructions": {
                          "rail": "pix",
                          "intent_id": "6f499bc1-bf2a-49ef-a1f2-86ec5acc48d1",
                          "br_code": "00020126580014br.gov.bcb.pix...",
                          "qr_code_image": "data:image/png;base64,iVBORw0KGgo...",
                          "expires_at": "2026-07-12T18:16:00Z"
                        }
                      },
                      "destination": {
                        "type": "wallet",
                        "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                      },
                      "source_leg": {
                        "status": "pending"
                      },
                      "destination_leg": {
                        "status": "pending"
                      },
                      "amounts": {
                        "source": {
                          "amount": "50000.00",
                          "currency": "BRL"
                        },
                        "target": {
                          "amount": "9405.000000",
                          "currency": "USDT"
                        },
                        "rate": "0.1881"
                      },
                      "exchange_conditions": {
                        "spread_bps": 0
                      },
                      "created_at": "2026-07-12T18:01:00Z",
                      "updated_at": "2026-07-12T18:01:00Z"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/IdempotencyProcessing"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "description": "Well-formed but not executable. Codes: NOT_AVAILABLE (the selected non-BRL/Pix wallet or crypto leg is not available, or a `via` quote sent from something other than a Pix payment, on a test key, or with `facility_terms_accepted`), WALLET_REQUIRED (a `via` quote needs the customer's OZAV wallet for the stablecoin), TARGET_CURRENCY_NOT_AVAILABLE, UNSUPPORTED_COMBINATION (the source/destination pair is not defined), RAIL_NOT_AVAILABLE (no settlement-ready coverage for the requested rail/corridor), QUOTE_EXPIRED (request a new quote), QUOTE_NETWORK_MISMATCH (the quote priced the network cost for a different `source.chain`; request a new quote with the right one), QUOTE_WITHOUT_NETWORK_COST (the quote did not price the network cost; request a new quote with `source.chain`), CUSTOMER_NOT_ENABLED (the payout destination is not yet enabled for transfers), PROVISIONING_REQUIRED (the customer or destination is not yet provisioned at the settlement layer), FACILITY_TERMS_VERSION_REQUIRED (the optional FX rate lock was accepted without `facility_terms_version`), and FACILITY_LOCK_UNAVAILABLE (the optional FX rate lock was refused by account state; nothing was created, and `details.reason` names the refusal - branch on it, not on the message).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "quote_expired": {
                    "summary": "The referenced quote is no longer valid",
                    "value": {
                      "error": {
                        "type": "invalid_request_error",
                        "code": "QUOTE_EXPIRED",
                        "message": "The quote has expired. Request a new quote before creating the transfer.",
                        "request_id": "req_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transfers \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"quote_id\": \"ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"source\": {\n      \"type\": \"fiat_payment\",\n      \"currency\": \"BRL\",\n      \"rail\": \"pix\"\n    },\n    \"destination\": {\n      \"type\": \"wallet\",\n      \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n      \"chain\": \"solana\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transfers\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"quote_id\": \"ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"source\": {\n      \"type\": \"fiat_payment\",\n      \"currency\": \"BRL\",\n      \"rail\": \"pix\"\n    },\n    \"destination\": {\n      \"type\": \"wallet\",\n      \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n      \"chain\": \"solana\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/transfers\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"quote_id\": \"ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"source\": {\n            \"type\": \"fiat_payment\",\n            \"currency\": \"BRL\",\n            \"rail\": \"pix\",\n        },\n        \"destination\": {\n            \"type\": \"wallet\",\n            \"wallet_id\": \"ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n            \"chain\": \"solana\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listTransfers",
        "tags": [
          "Transfers"
        ],
        "summary": "List transfers",
        "description": "Lists transfers, newest first, with the standard cursor pagination.\nFilter by `customer_id` and/or `status` (a `TransferStatus` value).\nReturns a `Page` of `Transfer`. BRL/Pix paths create real transfer\nrecords; combinations whose settlement rails are not enabled still fail\nclosed before a transfer is persisted.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter transfers by their current `TransferStatus`.",
            "schema": {
              "$ref": "#/components/schemas/TransferStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated transfers.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Transfer"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of transfers",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "transfer",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "quote_id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "status": "completed",
                          "source": {
                            "type": "fiat_payment",
                            "currency": "BRL",
                            "rail": "pix"
                          },
                          "destination": {
                            "type": "payout_destination",
                            "payout_destination_id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                          },
                          "source_leg": {
                            "status": "completed",
                            "settled_at": "2026-07-12T18:06:00Z"
                          },
                          "destination_leg": {
                            "status": "completed",
                            "settled_at": "2026-07-12T18:06:30Z"
                          },
                          "amounts": {
                            "source": {
                              "amount": "50000.00",
                              "currency": "BRL"
                            },
                            "target": {
                              "amount": "9405.00",
                              "currency": "USD"
                            },
                            "rate": "0.1881"
                          },
                          "created_at": "2026-07-12T18:01:00Z",
                          "updated_at": "2026-07-12T18:06:30Z",
                          "completed_at": "2026-07-12T18:06:30Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transfers \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transfers\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/transfers\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/transfers/{transfer_id}": {
      "parameters": [
        {
          "name": "transfer_id",
          "in": "path",
          "required": true,
          "description": "The opaque transfer id (`ozav_tr_...`).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getTransfer",
        "tags": [
          "Transfers"
        ],
        "summary": "Retrieve a transfer",
        "description": "Retrieves a transfer by its `ozav_tr_` id, including its `status` and the\nindependent `source_leg` / `destination_leg` settlement states. Poll this\nto track a transfer to completion. BRL/Pix paths create real\ntransfer records; other combinations may remain gated.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The transfer with per-leg settlement status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                },
                "examples": {
                  "in_flight": {
                    "summary": "Payment confirmed, payout not yet initiated",
                    "value": {
                      "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "transfer",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "quote_id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "payment_confirmed",
                      "source": {
                        "type": "fiat_payment",
                        "currency": "BRL",
                        "rail": "pix"
                      },
                      "destination": {
                        "type": "payout_destination",
                        "payout_destination_id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                      },
                      "source_leg": {
                        "status": "completed"
                      },
                      "destination_leg": {
                        "status": "pending"
                      },
                      "amounts": {
                        "source": {
                          "amount": "50000.00",
                          "currency": "BRL"
                        },
                        "target": {
                          "amount": "9405.00",
                          "currency": "USD"
                        },
                        "rate": "0.1881"
                      },
                      "created_at": "2026-07-12T18:01:00Z",
                      "updated_at": "2026-07-12T18:03:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "patch": {
        "operationId": "updateTransfer",
        "tags": [
          "Transfers"
        ],
        "summary": "Update mutable transfer attributes (metadata)",
        "description": "Updates the mutable attributes of a transfer - currently just\n`metadata`. Does not alter money movement. Returns the updated\n`Transfer`.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "metadata": {
                    "$ref": "#/components/schemas/Metadata"
                  }
                }
              },
              "examples": {
                "metadata": {
                  "summary": "Attach your own reference",
                  "value": {
                    "metadata": {
                      "invoice": "INV-2026-0042"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated transfer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                },
                "examples": {
                  "metadata_attached": {
                    "summary": "Metadata attached to the transfer",
                    "value": {
                      "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "transfer",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "quote_id": "ozav_q_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "completed",
                      "source": {
                        "type": "fiat_payment",
                        "currency": "BRL",
                        "rail": "pix"
                      },
                      "destination": {
                        "type": "payout_destination",
                        "payout_destination_id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                      },
                      "amounts": {
                        "source": {
                          "amount": "50000.00",
                          "currency": "BRL"
                        },
                        "target": {
                          "amount": "9405.00",
                          "currency": "USD"
                        },
                        "rate": "0.1881"
                      },
                      "created_at": "2026-07-12T18:01:00Z",
                      "updated_at": "2026-07-12T18:07:00Z",
                      "metadata": {
                        "invoice": "INV-2026-0042"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"metadata\": {\n      \"invoice\": \"INV-2026-0042\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"metadata\": {\n      \"invoice\": \"INV-2026-0042\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"metadata\": {\n            \"invoice\": \"INV-2026-0042\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/transfers/{transfer_id}/documents": {
      "post": {
        "operationId": "uploadTransferDocument",
        "tags": [
          "Transfers"
        ],
        "summary": "Attach the supporting document (invoice) for a payout",
        "description": "Attaches the commercial supporting document for a payout transfer: in\npractice the invoice the payment settles. The receiving institution\nvalidates the purpose code against this document, so a payout can be\naccepted and then sit unsettled while it is missing.\n\nOne document per transfer. Upload it before the payout is dispatched:\nthe document is forwarded to the receiving institution only after the\npayment order exists, so this response does not say whether it has\nbeen forwarded.\n\nThe bytes travel inline as base64 in a single call, unlike customer\ndocuments, which use a two-call signed upload. A payout document is one\nsmall file per operation, and a single call makes the result\nindivisible, with no window where the document is registered and its\nbytes are absent.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "transfer_id",
            "in": "path",
            "required": true,
            "description": "The opaque transfer id (`ozav_tr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutDocumentUpload"
              },
              "examples": {
                "invoice_pdf": {
                  "summary": "The invoice the payout settles",
                  "value": {
                    "file_name": "invoice-2026-08.pdf",
                    "content_type": "application/pdf",
                    "content_base64": "JVBERi0xLjQKJeLjz9MK"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document stored and linked to the payout.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutDocument"
                },
                "examples": {
                  "stored": {
                    "summary": "Stored, not yet forwarded",
                    "value": {
                      "id": "3f1a2b4c-5d6e-4f70-8a91-b2c3d4e5f607",
                      "object": "payout_document",
                      "transfer_id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "file_name": "invoice-2026-08.pdf",
                      "content_type": "application/pdf",
                      "size_bytes": 20
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "This transfer already has a supporting document."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"file_name\": \"invoice-2026-08.pdf\",\n    \"content_type\": \"application/pdf\",\n    \"content_base64\": \"JVBERi0xLjQKJeLjz9MK\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"file_name\": \"invoice-2026-08.pdf\",\n    \"content_type\": \"application/pdf\",\n    \"content_base64\": \"JVBERi0xLjQKJeLjz9MK\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/documents\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"file_name\": \"invoice-2026-08.pdf\",\n        \"content_type\": \"application/pdf\",\n        \"content_base64\": \"JVBERi0xLjQKJeLjz9MK\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/transfers/{transfer_id}/travel-rule": {
      "post": {
        "operationId": "submitTravelRule",
        "tags": [
          "Transfers"
        ],
        "summary": "Submit travel-rule data for a transfer",
        "description": "Attaches originator/beneficiary travel-rule information to a transfer\nwhose destination is an external crypto address. Required by policy above\na server-enforced amount threshold; it may also be supplied inline on\n`POST /transfers`. Returns the updated `Transfer`. Crypto-address\ntransfer legs remain gated until enabled.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "transfer_id",
            "in": "path",
            "required": true,
            "description": "The opaque transfer id (`ozav_tr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TravelRule"
              },
              "examples": {
                "originator_beneficiary": {
                  "summary": "Originator + beneficiary for a crypto send",
                  "value": {
                    "originator": {
                      "name": "Acme Ltda",
                      "account": "0xA97b1f0c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70"
                    },
                    "beneficiary": {
                      "name": "Jane Roe",
                      "account": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Travel-rule data accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                },
                "examples": {
                  "accepted": {
                    "summary": "Travel-rule data recorded on the transfer",
                    "value": {
                      "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "transfer",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "awaiting_payment",
                      "source": {
                        "type": "fiat_payment",
                        "currency": "BRL",
                        "rail": "pix"
                      },
                      "destination": {
                        "type": "payout_destination",
                        "payout_destination_id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                      },
                      "amounts": {
                        "source": {
                          "amount": "50000.00",
                          "currency": "BRL"
                        },
                        "target": {
                          "amount": "9405.00",
                          "currency": "USD"
                        },
                        "rate": "0.1881"
                      },
                      "travel_rule": {
                        "originator": {
                          "name": "Acme Ltda",
                          "account": "0xA97b1f0c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70"
                        },
                        "beneficiary": {
                          "name": "Jane Roe",
                          "account": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8"
                        }
                      },
                      "created_at": "2026-07-12T18:01:00Z",
                      "updated_at": "2026-07-12T18:02:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/travel-rule \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"originator\": {\n      \"name\": \"Acme Ltda\",\n      \"account\": \"0xA97b1f0c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70\"\n    },\n    \"beneficiary\": {\n      \"name\": \"Jane Roe\",\n      \"account\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/travel-rule\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"originator\": {\n      \"name\": \"Acme Ltda\",\n      \"account\": \"0xA97b1f0c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70\"\n    },\n    \"beneficiary\": {\n      \"name\": \"Jane Roe\",\n      \"account\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/travel-rule\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"originator\": {\n            \"name\": \"Acme Ltda\",\n            \"account\": \"0xA97b1f0c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70\",\n        },\n        \"beneficiary\": {\n            \"name\": \"Jane Roe\",\n            \"account\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/transfers/{transfer_id}/simulate": {
      "post": {
        "operationId": "simulateTransferPayment",
        "tags": [
          "Transfers"
        ],
        "summary": "Confirm the pay-in for a sandbox transfer",
        "description": "Sandbox affordance. Confirms the incoming payment on a transfer that is\nsitting in `awaiting_payment`, so you can drive the state machine past\nthe one step no test key can reach on its own: in `test` there is no\nreal payer and no payment confirmation, so an on-ramp would otherwise wait\nin `awaiting_payment` until it expires.\n\nThe transfer advances through both legs, in two steps, exactly as a real\nconfirmed payment would: the pay-in leg settles (`payment_confirmed`,\nemitting `transfer.updated`), then the destination leg settles and the\ntransfer reaches a terminal `completed`, emitting `transfer.completed`.\nBoth events are delivered and both appear on `GET /events`, in that\norder - the response body carries the terminal state. The wallet balance\non `GET /wallets/{wallet_id}/balances` moves with it.\n\n\nOne case still stops at `payment_confirmed`: a transfer whose opening\nleg was settled by a real rail rather than by the sandbox.\nFabricating a completion there would claim money arrived and was\ndelivered when neither happened.\n\n**Only on-ramp and fx transfers can be simulated**, because they are the\nonly ones with an incoming payment to confirm. An off-ramp opens on its\npayout leg and a wallet transfer is a custody move, so neither has a\npayer to simulate; both are refused with `409\nTRANSFER_NOT_AWAITING_PAYMENT`, and so is a transfer that has already\nmoved past `awaiting_payment`. Create a new transfer to test another\noutcome.\n\n**Test keys only.** A live key is refused with `403 SANDBOX_ONLY`; the\nenvironment comes from the key prefix (`ozav_sk_test_` vs\n`ozav_sk_live_`), never from the URL.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "transfers:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "transfer_id",
            "in": "path",
            "required": true,
            "description": "The opaque transfer id (`ozav_tr_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The pay-in was confirmed and the transfer advanced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                },
                "examples": {
                  "confirmed": {
                    "summary": "Sandbox on-ramp advanced to completed",
                    "value": {
                      "id": "ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "transfer",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "completed",
                      "source": {
                        "type": "fiat_payment",
                        "currency": "BRL",
                        "rail": "pix"
                      },
                      "destination": {
                        "type": "wallet",
                        "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                      },
                      "amounts": {
                        "source": {
                          "amount": "500.00",
                          "currency": "BRL"
                        },
                        "target": {
                          "amount": "89.55",
                          "currency": "USDC"
                        },
                        "rate": "0.1791"
                      },
                      "source_leg": {
                        "status": "completed",
                        "settled_at": "2026-07-12T18:03:00Z"
                      },
                      "destination_leg": {
                        "status": "completed",
                        "settled_at": "2026-07-12T18:03:00Z"
                      },
                      "created_at": "2026-07-12T18:01:00Z",
                      "updated_at": "2026-07-12T18:03:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/simulate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/simulate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1/simulate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/accounts": {
      "post": {
        "operationId": "createAccount",
        "tags": [
          "Accounts"
        ],
        "summary": "Request a customer's named fiat account",
        "description": "Creates or returns a customer's named fiat receiving account. The same\nendpoint serves every currency and rail; which ones can be opened is in\n`Rail.x-ozav-rail-availability`.\n\nThe customer must already be approved. In sandbox, BRL/BR/pix\nmaterializes a simulated account with deterministic Pix and\nbank-account instructions; no real bank account is opened and no real\nfunds can move. Every corridor OZAV serves uses this same\ncontract, and a corridor OZAV cannot currently open answers a named\nerror rather than a partial account, so your integration learns the\noutcome from the response instead of from a status label. The public\nresponse never exposes the banking venue behind the account.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountCreateRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "The whole request - OZAV picks the corridor",
                  "description": "Ask for an account in a currency. OZAV resolves the country and\nrail, because where the receiving bank sits is a fact about OZAV,\nnot about you. If that currency ever has more than one live\ncorridor, the call is refused with `ACCOUNT_RAIL_AMBIGUOUS` and\nthe candidates named, so nothing is chosen on your behalf.",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "currency": "USD"
                  }
                },
                "declared_corridor": {
                  "summary": "Pinning the corridor yourself",
                  "description": "Send `country` and `rail` when you want a specific one. The\nrequest behaves exactly as before, including the refusal when the\ncorridor you named is not open.",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "account_type": "named_fiat_account",
                    "currency": "BRL",
                    "country": "BR",
                    "rail": "pix",
                    "metadata": {
                      "label": "primary"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An account already exists for this customer and was returned idempotently.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "201": {
            "description": "Account created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                },
                "examples": {
                  "sandbox_active": {
                    "summary": "Sandbox account",
                    "value": {
                      "id": "ozav_acc_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "account",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "account_type": "named_fiat_account",
                      "currency": "BRL",
                      "country": "BR",
                      "rail": "pix",
                      "status": "active",
                      "holder": {
                        "name": "Ana Ramos",
                        "type": "individual"
                      },
                      "receiving_instructions": {
                        "pix": {
                          "key": "sandbox+01j8z9k2m3n4p5q6r7s8t9u0@developers.ozav.io",
                          "key_type": "email"
                        },
                        "bank_account": {
                          "branch": "0001",
                          "account_number": "9123456789-5",
                          "account_type": "payment"
                        }
                      },
                      "capabilities": {
                        "receive": true,
                        "send": false
                      },
                      "balance": null,
                      "metadata": {
                        "label": "primary"
                      },
                      "created_at": "2026-07-22T12:00:00Z",
                      "updated_at": "2026-07-22T12:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"currency\": \"USD\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"currency\": \"USD\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/accounts\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"currency\": \"USD\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listAccounts",
        "tags": [
          "Accounts"
        ],
        "summary": "List named accounts",
        "description": "Lists named fiat accounts with standard cursor pagination. Filter with\n`customer_id`, `currency`, `country`, `rail`, or `status` to recover the\naccount created after sandbox KYC approval or live opening. Returns\nonly accounts visible in the current key environment.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "description": "Filter by account currency.",
            "schema": {
              "$ref": "#/components/schemas/Currency"
            }
          },
          {
            "name": "country",
            "in": "query",
            "description": "Filter by account country or region code.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            }
          },
          {
            "name": "rail",
            "in": "query",
            "description": "Filter by receiving rail.",
            "schema": {
              "$ref": "#/components/schemas/Rail"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by public account status.",
            "schema": {
              "$ref": "#/components/schemas/AccountStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/accounts\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/accounts/{account_id}": {
      "get": {
        "operationId": "getAccount",
        "tags": [
          "Accounts"
        ],
        "summary": "Retrieve a named account",
        "description": "Retrieves a named fiat account by its opaque `ozav_acc_` id. The\naccount must belong to the caller and match the current key environment.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The opaque account id (`ozav_acc_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts/{account_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts/{account_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/accounts/{account_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/accounts/{account_id}/movements": {
      "get": {
        "operationId": "listAccountMovements",
        "tags": [
          "Accounts"
        ],
        "summary": "List an account's statement",
        "description": "Lists the credits that arrived in a named fiat account, newest first, as the\naccount's institution reports them. Where the institution exposes a live\nstatement, it is read at request time, not from an OZAV copy, so it can lag the\ninstitution by a few seconds and it is never older than the account's\n`balance.as_of`.\n\nA BRL account lists the Pix credits OZAV has already read from its statement,\nwhich happens every 10 minutes, so a credit can take up to that long to appear.\nThese are the same credits that move `balance`, and each one also fires\n`account.credited`. The 100 newest are returned. Sandbox BRL accounts are\nincluded: `POST /accounts/{account_id}/simulate/credit` adds a credit to them.\n\nEach item is a credit into the account. `reference` is the text the payer sent\nwith the transfer, when the rail carries one; `received` is present only when the\ninstitution reports the amount that actually landed separately from the amount\nthe payer sent (international wires lose correspondent charges on the way).\n\n`501 ACCOUNT_MOVEMENTS_NOT_AVAILABLE` means this account's institution does not\nexpose a statement through OZAV (sandbox accounts in other currencies included);\n`503 ACCOUNT_MOVEMENTS_UNAVAILABLE` means it does, and the read failed - retry.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The opaque account id (`ozav_acc_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The account's credits, newest first. `has_more` is always `false` today - the full page the institution returns (the 100 newest, on a BRL account) is served.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AccountMovement"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "9f2c1d",
                      "object": "account_movement",
                      "direction": "credit",
                      "amount": "965.00",
                      "currency": "USD",
                      "occurred_at": "2026-09-05T10:00:00Z",
                      "reference": "PAYMENT REF INVOICE 77",
                      "payer": {
                        "name": "ACME LLC"
                      },
                      "received": {
                        "amount": "930.00",
                        "currency": "USD"
                      }
                    }
                  ],
                  "has_more": false,
                  "next_cursor": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "501": {
            "$ref": "#/components/responses/NotImplementedYet"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts/{account_id}/movements \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts/{account_id}/movements\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/accounts/{account_id}/movements\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/accounts/{account_id}/simulate/credit": {
      "post": {
        "operationId": "simulateAccountCredit",
        "tags": [
          "Accounts"
        ],
        "summary": "Simulate a Pix credit into a sandbox BRL account (sandbox only)",
        "description": "Simulates a Pix that a payer sends into a sandbox BRL account. The credit is\nadded to the account's `balance`, listed on `GET /accounts/{account_id}/movements`\nand announced with `account.credited`, the same three things a real Pix into a\nlive account produces. Use it to build and test your deposit handling before a\nlive account receives money.\n\nTo test the conversion, create a transfer with `source.type: account` from this\naccount to the customer's wallet. In the sandbox the balance is debited at once,\nthe transfer completes, and the refusals are the live ones: below the BRL 1,000\nfloor, or above the balance not already committed to an open order\n(`422 ACCOUNT_BALANCE_INSUFFICIENT`).\n\nAvailable only to test-environment API keys on sandbox BRL accounts. A live\nkey, or an account in another currency, gets `404 SIMULATION_NOT_AVAILABLE`.\n\nThis route takes no `Idempotency-Key`: every call is a new credit.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The opaque account id (`ozav_acc_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,2})?$",
                    "description": "The amount the payer sends, in BRL, as a decimal string with at most 2 decimal places. Must be positive.",
                    "example": "1500.00"
                  }
                }
              },
              "examples": {
                "pix": {
                  "summary": "A Pix of R$ 1,500.00",
                  "value": {
                    "amount": "1500.00"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The credit, in the same shape `GET /accounts/{account_id}/movements` lists it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountMovement"
                },
                "example": {
                  "id": "ozav_amov_7c1e2d3f4a5b4c6d8e9f0a1b2c3d4e5f",
                  "object": "account_movement",
                  "direction": "credit",
                  "amount": "1500.00",
                  "currency": "BRL",
                  "occurred_at": "2026-10-06T12:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts/{account_id}/simulate/credit \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"amount\": \"1500.00\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts/{account_id}/simulate/credit\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"amount\": \"1500.00\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/accounts/{account_id}/simulate/credit\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"amount\": \"1500.00\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/accounts/{account_id}/deactivate": {
      "post": {
        "operationId": "deactivateAccount",
        "tags": [
          "Accounts"
        ],
        "summary": "Deactivate a sandbox account",
        "description": "Suspends a sandbox BRL/Pix account so you can test inactive-account\nhandling. Live account lifecycle changes remain gated until the\noperating procedure for your organization is enabled.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The opaque account id (`ozav_acc_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Account deactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts/{account_id}/deactivate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts/{account_id}/deactivate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/accounts/{account_id}/deactivate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/accounts/{account_id}/reactivate": {
      "post": {
        "operationId": "reactivateAccount",
        "tags": [
          "Accounts"
        ],
        "summary": "Reactivate a sandbox account",
        "description": "Reactivates a sandbox BRL/Pix account after a deactivate test. Live\naccount lifecycle changes remain gated until the operating procedure\nfor your organization is enabled.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The opaque account id (`ozav_acc_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Account reactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts/{account_id}/reactivate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts/{account_id}/reactivate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/accounts/{account_id}/reactivate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/accounts/{account_id}/payouts": {
      "post": {
        "operationId": "createAccountPayout",
        "tags": [
          "Accounts"
        ],
        "summary": "Pay out from a named account to a payout destination",
        "description": "Sends fiat from the customer's own named account to one of that\ncustomer's registered payout destinations, in the account's currency.\nNo conversion happens on this endpoint: the destination must be in the\nsame currency as the account (use `POST /transfers` to convert first).\n\nThe payment leaves **the customer's account**, with the customer as the\nsender of record. It is refused, with a named code, when the account\ncannot send yet or the customer who owns it is not approved and active\n(`422 ACCOUNT_NOT_READY`, with the exact reason in `details.reason`), when\na compliance hold applies to that customer (`403 COMPLIANCE_HOLD`), when the customer profile\nlacks fields the payment rail requires\n(`422 ACCOUNT_PAYOUT_SENDER_INCOMPLETE`), when the destination belongs to\na different customer (`422 PAYOUT_DESTINATION_NOT_OWNED`) or lacks data\nthe rail requires (`422 PAYOUT_DESTINATION_INCOMPLETE`), when the amount\nis above the per-payout limit for your organization\n(`422 ACCOUNT_PAYOUT_ABOVE_LIMIT`), or when the account's available\nbalance does not cover it - payouts still in flight count\n(`422 ACCOUNT_BALANCE_INSUFFICIENT`). Payouts from named accounts are\nenabled per organization: until yours is, the endpoint answers\n`403 ACCOUNT_PAYOUT_NOT_ENABLED`.\n\n**BRL by Pix key.** A BRL account pays only to an account of the same\nholder: the destination document must be the account holder's\n(`422 PAYOUT_DESTINATION_HOLDER_MISMATCH`). Before anything is sent, OZAV\nasks the Central Bank of Brazil who owns the Pix key, with the same codes\n`POST /transfers` uses: a key of someone else is refused\n(`422 DESTINATION_PIX_KEY_HOLDER_MISMATCH`), a key that does not exist too\n(`422 DESTINATION_PIX_KEY_NOT_FOUND`), and when the check cannot be\ncompleted the payout is refused with\n`503 PAYOUT_DESTINATION_HOLDER_UNVERIFIABLE` - nothing was sent, and\nretrying the same request later is safe; if it keeps failing, the\nCentral Bank's answer for that key does not show the holder's document,\nso pay to another key of the same holder. Unlike `POST /transfers`, a key\nwhose holder cannot be confirmed is never paid here. The destination\nneeds the holder's document (`422 PAYOUT_DESTINATION_INCOMPLETE` without\nit). Where the Pix-key check is not available for the account,\nPix-key payouts answer `403 ACCOUNT_PAYOUT_NOT_ENABLED`. Repeating a\npayout that already went out returns it with `200` before any of these\nchecks, and before the destination is checked again.\n\n**`reference` is the idempotency lever.** Repeating a request with the\nsame `reference` and `amount` for the same account returns the existing\npayout with `200` instead of sending a second payment. The destination\nis not part of that match: the `200` shows the destination the existing\npayout went to, even when the repeated request names another one. It\nalso travels to the receiving bank as the remittance information.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "accounts:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "description": "The opaque account id (`ozav_acc_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountPayoutCreateRequest"
              },
              "examples": {
                "usdToOwnBank": {
                  "summary": "Pay USD from the customer's account to their registered bank",
                  "value": {
                    "amount": "50.00",
                    "destination": {
                      "type": "payout_destination",
                      "payout_destination_id": "ozav_pd_01J00000000000000000000000"
                    },
                    "reference": "INV 0042"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same payout already existed (same `reference` and `amount`); nothing was sent again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountPayout"
                }
              }
            }
          },
          "201": {
            "description": "Payout accepted by the payment institution and recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountPayout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "502": {
            "description": "The payment institution did not accept the order (`ACCOUNT_PAYOUT_DISPATCH_FAILED`). Nothing left the account; read `GET /accounts/{account_id}/movements` before retrying.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/accounts/{account_id}/payouts \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"amount\": \"50.00\",\n    \"destination\": {\n      \"type\": \"payout_destination\",\n      \"payout_destination_id\": \"ozav_pd_01J00000000000000000000000\"\n    },\n    \"reference\": \"INV 0042\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/accounts/{account_id}/payouts\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"amount\": \"50.00\",\n    \"destination\": {\n      \"type\": \"payout_destination\",\n      \"payout_destination_id\": \"ozav_pd_01J00000000000000000000000\"\n    },\n    \"reference\": \"INV 0042\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/accounts/{account_id}/payouts\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"amount\": \"50.00\",\n        \"destination\": {\n            \"type\": \"payout_destination\",\n            \"payout_destination_id\": \"ozav_pd_01J00000000000000000000000\",\n        },\n        \"reference\": \"INV 0042\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/payout-destinations": {
      "post": {
        "operationId": "createPayoutDestination",
        "tags": [
          "Payout Destinations"
        ],
        "summary": "Register a payout destination",
        "description": "One canonical resource for every payout rail. `details` is a\ndiscriminated union keyed by `rail`. BRL/Pix destinations use a Pix key\n(`details.pix_key`). Do not submit a Pix BR code here: BR codes are\npay-in instructions for a specific charge, not reusable payout\ndestinations. Rails that require external venue provisioning may still\nfail closed until enabled.\n\n#### Which corridor, which rail, which `details`\n\nA payout destination is a third party you pay. It is not the same thing\nas a named account you hold at OZAV, and the two do not share a table:\n`Rail.x-ozav-rail-availability` is about opening an account, and it does\nnot describe the corridors below. OZAV keeps no nominal account in yen\nor in rand, and still pays a beneficiary in both.\n\n| Currency | `rail` | `details` shape | Required in `details` | Also required by the corridor | Availability |\n|---|---|---|---|---|---|\n| BRL | `pix` | Pix | `pix_key` | - | live |\n| USD | `fedwire` | UsBank | `account_number`, `routing_number` | - | live |\n| EUR | `sepa` | Sepa | `iban` | - | live |\n| GBP | `faster_payments` | FasterPayments | `account_number`, `sort_code` | a `business` account holder; an `individual` is refused with `422 ACCOUNT_HOLDER_TYPE_NOT_SUPPORTED` | live |\n| MXN | `spei` | Spei | `clabe` | - | live |\n| AED | `uaefts` | IbanLocal | `iban` | `bic` | live |\n| JPY | `zengin` | LocalBank | `account_number` | `bank_code`, `branch_code` | live |\n| NGN | `nip` | LocalBank | `account_number` | `bank_code`, a contact on the holder | live |\n| PEN | `cce` | LocalBank | `account_number` | `bic`, a contact, postal code and state on the holder, and `nationality` for an individual | live |\n\nEvery corridor in the table is live for every organization with access to payouts.\n\n**Not currently served for payouts:** `USD/swift`, `EUR/swift`,\n`CAD/eft`, `CNY/cnaps` and `ZAR/rtc`. Registering a destination in one of them is refused.\n`ach` and `co_transfer` are not offered and cannot be requested. A `GBP` destination\nis registered on `faster_payments`: `swift` destinations are `USD` only.\nThe `rail` values stay in the enum\nso records that already carry them keep reading. A corridor enters the\ntable above only when it pays.\n\n**\"Also required by the corridor\" is not required to register.** Those\nfields are enforced when the payment is created, with a refusal that\nnames the field - because a destination that serves one corridor must\nnot be invalidated the day another corridor asks for more. The\n`bank_code` values in NGN and JPY come from a closed directory; the\nrefusal carries the accepted list.\n\n`account_holder.birth_date` is required for every individual\nbeneficiary in the corridors above, and `bank_name` is required on the\nrails that have a bank. Both are accepted here.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "destinations:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutDestinationCreateRequest"
              },
              "examples": {
                "pix": {
                  "summary": "BRL / PIX - a PIX key",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "currency": "BRL",
                    "rail": "pix",
                    "account_holder": {
                      "name": "Ana Ramos",
                      "type": "individual"
                    },
                    "details": {
                      "pix_key": "ana@example.com"
                    }
                  }
                },
                "zengin": {
                  "summary": "JPY / zengin - the richest corridor, and an individual",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "currency": "JPY",
                    "rail": "zengin",
                    "account_holder": {
                      "name": "Kenji Tanaka",
                      "type": "individual",
                      "birth_date": "1985-04-12",
                      "address": {
                        "line1": "2-7-3 Marunouchi",
                        "city": "Tokyo",
                        "state": "Tokyo",
                        "postal_code": "100-0005",
                        "country": "JP"
                      }
                    },
                    "details": {
                      "account_number": "1234567",
                      "bank_code": "0001",
                      "branch_code": "001",
                      "bank_name": "MUFG Bank"
                    }
                  }
                },
                "uaefts": {
                  "summary": "AED / uaefts - the account is an IBAN, and the bank is the BIC",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "currency": "AED",
                    "rail": "uaefts",
                    "account_holder": {
                      "name": "Falcon Trading LLC",
                      "type": "business"
                    },
                    "details": {
                      "iban": "AE070331234567890123456",
                      "bic": "EBILAEAD",
                      "bank_name": "Emirates NBD"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payout destination registered.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutDestination"
                },
                "examples": {
                  "pix_active": {
                    "summary": "Registered and active",
                    "value": {
                      "id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "payout_destination",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "currency": "BRL",
                      "rail": "pix",
                      "status": "active",
                      "account_holder": {
                        "name": "Ana Ramos",
                        "type": "individual"
                      },
                      "details": {
                        "pix_key": "ana@example.com"
                      },
                      "created_at": "2026-07-12T18:05:00Z"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "$ref": "#/components/responses/IdempotencyProcessing"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "501": {
            "$ref": "#/components/responses/NotImplementedYet"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/payout-destinations \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"currency\": \"BRL\",\n    \"rail\": \"pix\",\n    \"account_holder\": {\n      \"name\": \"Ana Ramos\",\n      \"type\": \"individual\"\n    },\n    \"details\": {\n      \"pix_key\": \"ana@example.com\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/payout-destinations\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"currency\": \"BRL\",\n    \"rail\": \"pix\",\n    \"account_holder\": {\n      \"name\": \"Ana Ramos\",\n      \"type\": \"individual\"\n    },\n    \"details\": {\n      \"pix_key\": \"ana@example.com\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/payout-destinations\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"currency\": \"BRL\",\n        \"rail\": \"pix\",\n        \"account_holder\": {\n            \"name\": \"Ana Ramos\",\n            \"type\": \"individual\",\n        },\n        \"details\": {\n            \"pix_key\": \"ana@example.com\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listPayoutDestinations",
        "tags": [
          "Payout Destinations"
        ],
        "summary": "List payout destinations",
        "description": "Lists a customer's payout destinations with the standard cursor\npagination; filter with the `customer_id` query parameter. Returns a\n`Page` of `PayoutDestination`. BRL/Pix key destinations can be created\nand read; externally provisioned rails may still be gated.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "destinations:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated payout destinations.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PayoutDestination"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of payout destinations",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "payout_destination",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "currency": "BRL",
                          "rail": "pix",
                          "status": "active",
                          "account_holder": {
                            "name": "Ana Ramos",
                            "type": "individual"
                          },
                          "details": {
                            "pix_key": "ana@example.com"
                          },
                          "created_at": "2026-07-12T18:05:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/payout-destinations \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/payout-destinations\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/payout-destinations\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/payout-destinations/{payout_destination_id}": {
      "parameters": [
        {
          "name": "payout_destination_id",
          "in": "path",
          "required": true,
          "description": "The opaque payout-destination id (`ozav_pd_...`).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getPayoutDestination",
        "tags": [
          "Payout Destinations"
        ],
        "summary": "Retrieve a payout destination",
        "description": "Retrieves a single payout destination by its `ozav_pd_` id, including its\n`status`, `rail`, and rail-specific `details`.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "destinations:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The payout destination.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutDestination"
                },
                "examples": {
                  "pix_active": {
                    "summary": "An active PIX destination",
                    "value": {
                      "id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "payout_destination",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "currency": "BRL",
                      "rail": "pix",
                      "status": "active",
                      "account_holder": {
                        "name": "Ana Ramos",
                        "type": "individual"
                      },
                      "details": {
                        "pix_key": "ana@example.com"
                      },
                      "created_at": "2026-07-12T18:05:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "patch": {
        "operationId": "updatePayoutDestination",
        "tags": [
          "Payout Destinations"
        ],
        "summary": "Update a payout destination",
        "description": "Updates a payout destination's mutable attributes (account holder,\nmetadata). Only included fields are changed. Returns the updated\n`PayoutDestination`.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "destinations:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "account_holder": {
                    "$ref": "#/components/schemas/AccountHolder"
                  },
                  "metadata": {
                    "$ref": "#/components/schemas/Metadata"
                  }
                }
              },
              "examples": {
                "holder": {
                  "summary": "Correct the account holder",
                  "value": {
                    "account_holder": {
                      "name": "Ana C. Ramos",
                      "type": "individual"
                    },
                    "metadata": {
                      "reviewed": "true"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated payout destination.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutDestination"
                },
                "examples": {
                  "holder_corrected": {
                    "summary": "Account holder corrected",
                    "value": {
                      "id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "payout_destination",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "currency": "BRL",
                      "rail": "pix",
                      "status": "active",
                      "account_holder": {
                        "name": "Ana C. Ramos",
                        "type": "individual"
                      },
                      "details": {
                        "pix_key": "ana@example.com"
                      },
                      "created_at": "2026-07-12T18:05:00Z",
                      "metadata": {
                        "reviewed": "true"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"account_holder\": {\n      \"name\": \"Ana C. Ramos\",\n      \"type\": \"individual\"\n    },\n    \"metadata\": {\n      \"reviewed\": \"true\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"account_holder\": {\n      \"name\": \"Ana C. Ramos\",\n      \"type\": \"individual\"\n    },\n    \"metadata\": {\n      \"reviewed\": \"true\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"account_holder\": {\n            \"name\": \"Ana C. Ramos\",\n            \"type\": \"individual\",\n        },\n        \"metadata\": {\n            \"reviewed\": \"true\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/payout-destinations/{payout_destination_id}/deactivate": {
      "post": {
        "operationId": "deactivatePayoutDestination",
        "tags": [
          "Payout Destinations"
        ],
        "summary": "Deactivate a payout destination",
        "description": "Deactivates a payout destination so it can no longer be used as a\ntransfer destination; existing transfers are unaffected. Idempotent -\ndeactivating an already-inactive destination is a no-op. Returns the\n`PayoutDestination` with `status: inactive`. Reverse it with the\nreactivate endpoint.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "destinations:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "payout_destination_id",
            "in": "path",
            "required": true,
            "description": "The opaque payout-destination id (`ozav_pd_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutDestination"
                },
                "examples": {
                  "inactive": {
                    "summary": "The destination is now inactive",
                    "value": {
                      "id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "payout_destination",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "currency": "BRL",
                      "rail": "pix",
                      "status": "inactive",
                      "account_holder": {
                        "name": "Ana Ramos",
                        "type": "individual"
                      },
                      "details": {
                        "pix_key": "ana@example.com"
                      },
                      "created_at": "2026-07-12T18:05:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deactivate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/payout-destinations/{payout_destination_id}/reactivate": {
      "post": {
        "operationId": "reactivatePayoutDestination",
        "tags": [
          "Payout Destinations"
        ],
        "summary": "Reactivate a payout destination",
        "description": "Reactivates a previously deactivated payout destination, making it usable\nagain as a transfer destination. Idempotent. Returns the\n`PayoutDestination` with `status: active`.\n",
        "x-ozav-entitlement": "liquidity",
        "x-ozav-scopes": [
          "destinations:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "payout_destination_id",
            "in": "path",
            "required": true,
            "description": "The opaque payout-destination id (`ozav_pd_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutDestination"
                },
                "examples": {
                  "active_again": {
                    "summary": "The destination is active again",
                    "value": {
                      "id": "ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "payout_destination",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "currency": "BRL",
                      "rail": "pix",
                      "status": "active",
                      "account_holder": {
                        "name": "Ana Ramos",
                        "type": "individual"
                      },
                      "details": {
                        "pix_key": "ana@example.com"
                      },
                      "created_at": "2026-07-12T18:05:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1/reactivate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1/reactivate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/payout-destinations/ozav_pd_01J8Z9K2M3N4P5Q6R7S8T9U0V1/reactivate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards": {
      "post": {
        "operationId": "createCard",
        "tags": [
          "Cards"
        ],
        "summary": "Issue a virtual card",
        "description": "Issues an OZAV virtual card for a customer who is eligible for one.\nEligibility is two conditions, not one: `verification.status` is\n`approved` and the customer's account is active. Approval alone is not\nenough, and a customer who never opened an account never becomes\neligible. Missing either one returns `422\nCUSTOMER_NOT_CARD_ELIGIBLE`. In production the card spends the USDC in\nthe customer's OZAV-managed wallet (see `funding_type` on `Card`), so a\ncustomer without one is refused with `422 CUSTOMER_WALLET_REQUIRED`:\ncreate them with `custody_model: managed`. `Idempotency-Key` is required. Sandbox keys create a simulated active card end-to-end;\nlive keys require the organization's live card program to be enabled.\n",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardCreateRequest"
              },
              "examples": {
                "virtual": {
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "type": "virtual",
                    "funding_type": "prepaid_balance"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Card issued.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                },
                "examples": {
                  "card": {
                    "value": {
                      "id": "ozav_card_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "card",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "type": "virtual",
                      "status": "active",
                      "funding_type": "prepaid_balance",
                      "last4": "4242",
                      "exp_month": 7,
                      "exp_year": 2029,
                      "brand_name": "OZAV",
                      "created_at": "2026-07-12T18:00:00Z",
                      "activated_at": "2026-07-12T18:00:00Z",
                      "cancelled_at": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"type\": \"virtual\",\n    \"funding_type\": \"prepaid_balance\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"type\": \"virtual\",\n    \"funding_type\": \"prepaid_balance\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"type\": \"virtual\",\n        \"funding_type\": \"prepaid_balance\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listCards",
        "tags": [
          "Cards"
        ],
        "summary": "List cards",
        "description": "Lists cards with standard cursor pagination; filter by `customer_id` or `status`.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "active",
                "frozen",
                "cancelled",
                "expired"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated cards.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Card"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/cards\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}": {
      "parameters": [
        {
          "name": "card_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Opaque card id, prefixed `ozav_card_`."
        }
      ],
      "get": {
        "operationId": "getCard",
        "tags": [
          "Cards"
        ],
        "summary": "Retrieve a card",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/cards/{card_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "patch": {
        "operationId": "updateCard",
        "tags": [
          "Cards"
        ],
        "summary": "Update a card's spending controls",
        "description": "Replaces the card's spending controls without re-issuing the card. The PAN, expiry and card id are unchanged, so recurring subscriptions on the card keep working.\n\n**This replaces `spending_controls` in full; it does not merge.** Send the complete object: anything you omit is cleared. There is no merge, because with nested lists (`spending_limits`, `blocked_categories`) it is ambiguous whether sending a list adds to the stored one or replaces it, and both readings are defensible. The request body is strict, so an unrecognised key returns `400` naming it rather than being silently dropped.\n\nCard state (`freeze`, `unfreeze`, `close`) is **not** settable here: those have their own routes, with state-transition guards and issuer ordering. A closed card returns `422`.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "spending_controls"
                ],
                "properties": {
                  "spending_controls": {
                    "$ref": "#/components/schemas/CardSpendingControls"
                  }
                }
              },
              "examples": {
                "change_limit": {
                  "summary": "Change the monthly limit without re-issuing the card",
                  "value": {
                    "spending_controls": {
                      "spending_limits": [
                        {
                          "amount": 500000,
                          "interval": "monthly"
                        }
                      ]
                    }
                  }
                },
                "clear_controls": {
                  "summary": "Remove every control - this replaces, it does not merge",
                  "value": {
                    "spending_controls": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id} \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"spending_controls\": {\n      \"spending_limits\": [\n        {\n          \"amount\": 500000,\n          \"interval\": \"monthly\"\n        }\n      ]\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"spending_controls\": {\n      \"spending_limits\": [\n        {\n          \"amount\": 500000,\n          \"interval\": \"monthly\"\n        }\n      ]\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/cards/{card_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"spending_controls\": {\n            \"spending_limits\": [\n                {\n                    \"amount\": 500000,\n                    \"interval\": \"monthly\",\n                },\n            ],\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/balance": {
      "parameters": [
        {
          "name": "card_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Opaque card id, prefixed `ozav_card_`."
        }
      ],
      "get": {
        "operationId": "getCardBalance",
        "tags": [
          "Cards"
        ],
        "summary": "Retrieve a card's spendable balance",
        "description": "How much this card can spend right now.\n\nThe balance belongs to the **cardholder**, not to the card: every card issued to the same customer draws on the same balance, so two cards for one holder will report the same number.\n\n`available_amount` is the amount an authorisation will actually clear. It is already net of everything the card has spent, including authorisations that have not yet settled, and it is rounded **down** to the cent, so the figure shown is always spendable.\n\nIn production this reads from the same USDC as `GET /wallets/{wallet_id}/balances`: the card spends the USDC in the cardholder's OZAV-managed wallet, and that route already deducts card spend from USDC. After any wallet movement (a deposit, a send, an off-ramp) this figure can differ from the wallet balance for a few minutes. The wallet figure is what the cardholder owns; this figure is what a purchase clears right now, and the two converge once OZAV reconciles them, usually within minutes. In the sandbox the two are independent: the card balance moves only with `POST /cards/{card_id}/simulate/funding` and simulated purchases.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The card's spendable balance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "card_id",
                    "available_amount",
                    "currency",
                    "as_of"
                  ],
                  "properties": {
                    "card_id": {
                      "type": "string",
                      "description": "The card this balance was read for.",
                      "example": "ozav_card_01J00000000000000000000000"
                    },
                    "available_amount": {
                      "type": "string",
                      "description": "Decimal string, rounded down to the cent. A string, not a number, so no client turns it into a float.",
                      "example": "250.50"
                    },
                    "currency": {
                      "type": "string",
                      "description": "The asset backing the balance.",
                      "example": "USDC"
                    },
                    "as_of": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the balance was read.",
                      "example": "2026-09-29T12:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/balance \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/balance\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/cards/{card_id}/balance\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/freeze": {
      "post": {
        "operationId": "freezeCard",
        "tags": [
          "Cards"
        ],
        "summary": "Freeze a card",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/freeze \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/freeze\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/freeze\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/unfreeze": {
      "post": {
        "operationId": "unfreezeCard",
        "tags": [
          "Cards"
        ],
        "summary": "Unfreeze a card",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/unfreeze \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/unfreeze\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/unfreeze\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/close": {
      "post": {
        "operationId": "closeCard",
        "tags": [
          "Cards"
        ],
        "summary": "Close a card",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/close \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/close\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/close\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/secure-display-session": {
      "post": {
        "operationId": "createCardSecureDisplaySession",
        "tags": [
          "Cards"
        ],
        "summary": "Create a secure card display session",
        "description": "Returns a short-lived OZAV-hosted URL for sensitive card display. PAN, CVV, and PIN are never returned by the API.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "card_display"
                    ],
                    "default": "card_display"
                  }
                }
              },
              "examples": {
                "open_session": {
                  "summary": "Open a session so the cardholder can see the full PAN",
                  "value": {
                    "purpose": "card_display"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Secure display session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardDisplaySession"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/secure-display-session \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"purpose\": \"card_display\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/secure-display-session\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"purpose\": \"card_display\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/secure-display-session\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"purpose\": \"card_display\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/simulate/funding": {
      "post": {
        "operationId": "simulateCardFunding",
        "tags": [
          "Cards"
        ],
        "summary": "Load the prepaid balance (sandbox only)",
        "description": "Credits the card holder's prepaid balance so you can exercise authorisation, limits and\ndeclines. **Sandbox only** - a `live` key gets `404 SIMULATION_NOT_AVAILABLE`, because in\nproduction the card spends the USDC in the cardholder's OZAV-managed wallet, which arrives by\ndeposit or transfer, never through a call that loads the card.\n\nWithout this, a customer's first card is created `active` and declines every purchase with\n`Insufficient balance` forever, so authorisation, spending limits, declines and interchange\ncould not be exercised at all.\n\nThe credit lands on the same balance the authorisation debits, and the amount is yours to\nchoose - so **declining for insufficient balance stays testable**: fund a small amount and\nsimulate a purchase above it.\n\nThe balance belongs to the **customer**, not to the card: funding here also funds that\ncustomer's other cards.\n",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "amount_usd"
                ],
                "properties": {
                  "amount_usd": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "How much to credit, in USD. Must be positive, with at most 2 decimal places."
                  }
                }
              },
              "example": {
                "amount_usd": 50
              },
              "examples": {
                "credit": {
                  "summary": "Credit the customer's balance so a card can be used",
                  "value": {
                    "amount_usd": 250
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Balance credited.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "object",
                    "card_id",
                    "amount_usd",
                    "balance_after_usd"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "description": "Always `card_funding_simulation`."
                    },
                    "card_id": {
                      "type": "string",
                      "description": "The `ozav_card_` id that was funded."
                    },
                    "amount_usd": {
                      "type": "number",
                      "description": "The amount credited."
                    },
                    "balance_after_usd": {
                      "type": "number",
                      "description": "The holder's balance after the credit, read back from the ledger."
                    }
                  }
                },
                "example": {
                  "object": "card_funding_simulation",
                  "card_id": "ozav_card_01J8XY0000000000000000000",
                  "amount_usd": 50,
                  "balance_after_usd": 50
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/simulate/funding \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"amount_usd\": 250\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/simulate/funding\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"amount_usd\": 250\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/simulate/funding\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"amount_usd\": 250,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/simulate/purchase": {
      "post": {
        "operationId": "simulateCardPurchase",
        "tags": [
          "Cards"
        ],
        "summary": "Simulate a card purchase (sandbox only)",
        "description": "Runs a purchase through the real authorisation decision and settles it if approved. A card purchase is initiated by a merchant, so unlike a transfer there is no client call to hang a simulator off; this route is that trigger. Available only to test-environment API keys: a live key receives 404 SIMULATION_NOT_AVAILABLE. The decision is not fabricated, so a card with a configured spending limit is genuinely declined here, which is the only way to test that your limits work before they matter.\nThis route takes no `Idempotency-Key`: every call is a new purchase. A `503 CARD_SIMULATION_NOT_SETTLED` means the purchase was authorised and debited but its settlement was not recorded - do not retry it; read `GET /cards/{card_id}/authorizations` first. `503 CARD_FUNDING_UNAVAILABLE` on a refund moved nothing and can be retried.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount_usd"
                ],
                "properties": {
                  "amount_usd": {
                    "type": "number",
                    "description": "Purchase amount in USD. Must be positive, with at most 2 decimal places.",
                    "example": 49.9
                  },
                  "merchant": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "Cafe Lisboa"
                      },
                      "category_code": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "MCC.",
                        "example": "5812"
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "example": "PT"
                      }
                    }
                  },
                  "is_card_present": {
                    "type": "boolean",
                    "default": false
                  },
                  "settle": {
                    "type": "boolean",
                    "default": true,
                    "description": "`false` leaves an approved purchase authorised and held, as on the card networks before settlement: `card.authorized` fires, the amount (and any surcharge) is debited, and the hold appears on `/authorizations` with `settled_at: null`. Then settle it with `POST /cards/{card_id}/simulate/settlement` (optionally with a different final amount, such as a tip) or release it with `POST /cards/{card_id}/simulate/reversal`, using the `authorization_ref` this call returns. A refund is never held."
                  },
                  "is_refund": {
                    "type": "boolean",
                    "default": false,
                    "description": "Simulate a merchant refund instead of a purchase. It credits the customer's balance and is not an authorization: card status, spending limits and fraud rules do not apply to money coming back. It returns no surcharge. It cannot exceed what this card's settled purchases debited (surcharges not included) and not yet had refunded - above that it is refused with `422 SIMULATION_REFUSED` and `param: amount_usd`. It is listed on `/transactions` with `direction: credit` and earns no interchange."
                  },
                  "presentment": {
                    "type": "object",
                    "description": "Simulate an international purchase: what it cost in the merchant's currency. Omit it to simulate a domestic one.\n\nSend the amount in the currency's minor units, as the card networks do - `5000` with `JPY` is five thousand yen, not fifty. You do not send the number of decimal places: it comes from the same ISO 4217 table the issuer adapter uses, so sandbox and production agree on what `5000 JPY` is worth.\n\nThe settled transaction then carries `presentment` on `GET /cards/{card_id}/transactions`, which is what lets you build and test your international-transaction handling before a real issuer is feeding the programme.",
                    "required": [
                      "amount_minor",
                      "currency"
                    ],
                    "properties": {
                      "amount_minor": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Minor units of `currency`. Non-negative integer.",
                        "example": 5000
                      },
                      "currency": {
                        "type": "string",
                        "description": "ISO 4217 code of the merchant's currency.",
                        "example": "JPY"
                      }
                    }
                  }
                }
              },
              "examples": {
                "international": {
                  "summary": "An international purchase (JPY merchant, USD card)",
                  "value": {
                    "amount_usd": 33.12,
                    "merchant": {
                      "name": "Ramen Ichiban",
                      "category_code": "5812",
                      "country": "JP"
                    },
                    "presentment": {
                      "amount_minor": 5000,
                      "currency": "JPY"
                    }
                  }
                },
                "domestic": {
                  "summary": "A domestic purchase - omit `presentment`",
                  "value": {
                    "amount_usd": 49.9,
                    "merchant": {
                      "name": "Cafe Lisboa",
                      "category_code": "5812",
                      "country": "PT"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decision, and the settled transaction reference when approved. A declined purchase settles nothing and returns a null transaction_ref.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "card_purchase_simulation"
                    },
                    "card_id": {
                      "type": "string"
                    },
                    "approved": {
                      "type": "boolean"
                    },
                    "decline_reason": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "authorization_ref": {
                      "type": "string",
                      "example": "sim_auth_9k2m..."
                    },
                    "transaction_ref": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "example": "sim_txn_4b8x..."
                    },
                    "settled": {
                      "type": "boolean",
                      "description": "`true` when the purchase settled in this call. `false` on an approval means it is held (`settle: false`)."
                    },
                    "client_surcharge": {
                      "$ref": "#/components/schemas/ClientSurcharge"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/simulate/purchase \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"amount_usd\": 33.12,\n    \"merchant\": {\n      \"name\": \"Ramen Ichiban\",\n      \"category_code\": \"5812\",\n      \"country\": \"JP\"\n    },\n    \"presentment\": {\n      \"amount_minor\": 5000,\n      \"currency\": \"JPY\"\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/simulate/purchase\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"amount_usd\": 33.12,\n    \"merchant\": {\n      \"name\": \"Ramen Ichiban\",\n      \"category_code\": \"5812\",\n      \"country\": \"JP\"\n    },\n    \"presentment\": {\n      \"amount_minor\": 5000,\n      \"currency\": \"JPY\"\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/simulate/purchase\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"amount_usd\": 33.12,\n        \"merchant\": {\n            \"name\": \"Ramen Ichiban\",\n            \"category_code\": \"5812\",\n            \"country\": \"JP\",\n        },\n        \"presentment\": {\n            \"amount_minor\": 5000,\n            \"currency\": \"JPY\",\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/simulate/settlement": {
      "post": {
        "operationId": "simulateCardSettlement",
        "tags": [
          "Cards"
        ],
        "summary": "Settle a held simulated purchase (sandbox only)",
        "description": "Settles a purchase that `POST /cards/{card_id}/simulate/purchase` left held (`settle: false`), through the same entry an issuer's settlement uses. Send `amount_usd` to settle a different final amount (a tip, a fuel top-up); the difference against the held amount is adjusted on the balance. `card.settled` fires with the same `transaction_id` as `card.authorized`. Test-environment keys only: a live key receives 404 SIMULATION_NOT_AVAILABLE. A purchase that is already settled or was reversed is refused with `422 SIMULATION_REFUSED`.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "authorization_ref"
                ],
                "properties": {
                  "authorization_ref": {
                    "type": "string",
                    "description": "The `authorization_ref` returned by simulate/purchase.",
                    "example": "sim_auth_9k2m..."
                  },
                  "amount_usd": {
                    "type": "number",
                    "description": "Final amount in USD. Omit it to settle the held amount. Must be positive, with at most 2 decimal places.",
                    "example": 57.5
                  }
                }
              },
              "examples": {
                "with_tip": {
                  "summary": "Settle a held purchase for a different final amount",
                  "value": {
                    "authorization_ref": "sim_auth_9k2m4b8x",
                    "amount_usd": 57.5
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settled transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "card_settlement_simulation"
                    },
                    "card_id": {
                      "type": "string"
                    },
                    "authorization_ref": {
                      "type": "string"
                    },
                    "transaction_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "example": "ozav_ctxn_2e9b3c1a5b7d4e2f9a1c8d3f5e7a9b1c"
                    },
                    "transaction_ref": {
                      "type": "string",
                      "example": "sim_txn_4b8x..."
                    },
                    "final_amount_usd": {
                      "type": "string",
                      "example": "57.50"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/simulate/settlement \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"authorization_ref\": \"sim_auth_9k2m4b8x\",\n    \"amount_usd\": 57.5\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/simulate/settlement\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"authorization_ref\": \"sim_auth_9k2m4b8x\",\n    \"amount_usd\": 57.5\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/simulate/settlement\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"authorization_ref\": \"sim_auth_9k2m4b8x\",\n        \"amount_usd\": 57.5,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/simulate/reversal": {
      "post": {
        "operationId": "simulateCardReversal",
        "tags": [
          "Cards"
        ],
        "summary": "Reverse a held simulated purchase (sandbox only)",
        "description": "Releases a purchase that `POST /cards/{card_id}/simulate/purchase` left held (`settle: false`), as when a merchant cancels an authorisation or it expires, through the same entry an issuer's release uses. The held amount and its surcharge return to the balance, the authorisation shows as `reversed` on `/authorizations`, and `card.reversed` fires with the same `transaction_id` as `card.authorized`. Test-environment keys only: a live key receives 404 SIMULATION_NOT_AVAILABLE. A purchase that is already settled or reversed is refused with `422 SIMULATION_REFUSED`.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "authorization_ref"
                ],
                "properties": {
                  "authorization_ref": {
                    "type": "string",
                    "description": "The `authorization_ref` returned by simulate/purchase.",
                    "example": "sim_auth_9k2m..."
                  }
                }
              },
              "examples": {
                "reverse": {
                  "summary": "Release a held purchase",
                  "value": {
                    "authorization_ref": "sim_auth_9k2m4b8x"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reversal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "example": "card_reversal_simulation"
                    },
                    "card_id": {
                      "type": "string"
                    },
                    "authorization_ref": {
                      "type": "string"
                    },
                    "reversed_amount_usd": {
                      "type": "string",
                      "example": "49.90"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/simulate/reversal \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"authorization_ref\": \"sim_auth_9k2m4b8x\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/simulate/reversal\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"authorization_ref\": \"sim_auth_9k2m4b8x\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/cards/{card_id}/simulate/reversal\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"authorization_ref\": \"sim_auth_9k2m4b8x\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/transactions": {
      "get": {
        "operationId": "listCardTransactions",
        "tags": [
          "Cards"
        ],
        "summary": "List card transactions",
        "description": "Settled card movement - money that actually cleared. The sandbox response is an empty list until simulated card activity is added.\n\n**This is how you reconcile card spend.** `card.authorized`, `card.declined`, `card.settled` and `card.refunded` fire as a card is used; book from this route, which lists settled purchases and refunds, and read `/authorizations` only for holds that are still open (`status` `pending` or `authorized`). A released hold raises no event.\n\n**Reading it incrementally.** Both card activity routes return rows newest first and take only `limit` (up to 100) and `starting_after` - there is no date filter. Rows are ordered by `created_at`, and a row's `status` and `settled_at` can change after it first appears without moving it back to the top. Re-read a recent window (for example the last seven days) on each pass rather than stopping at the first id you already stored, and key your copy by `id`. Poll only cards that still have an open hold (`status` `pending` or `authorized`). A card with no open hold needs no more reads until its next `card.authorized`, `card.settled` or `card.refunded`. Do not treat `reversed` or `declined` as immutable: a late capture turns the same row `settled`, and the row keeps its original `created_at`, which can be weeks old. Make each pass reach back at least to the oldest row you still hold as open (`pending` or `authorized`): a released hold raises no event, so a window shorter than that never sees it close. When a `card.settled` matches nothing in that window, page back until you find it, or re-read back to the oldest `reversed` or `declined` row you still track. Keep all polling well inside your organization's 90 requests per minute; on `429` or `503`, wait `Retry-After` when present and back off otherwise. A purchase OZAV declines is not listed on either route - it is on `GET /cards/{card_id}/declines`; a hold the card processor refuses after approval stays on `/authorizations` as `declined`.\n\nTo tell an international purchase from a domestic one, compare `presentment.currency` with `currency` - not `merchant.country`, which answers a different question.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Card transactions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardActivityPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/transactions \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/transactions\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/cards/{card_id}/transactions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/authorizations": {
      "get": {
        "operationId": "listCardAuthorizations",
        "tags": [
          "Cards"
        ],
        "summary": "List card authorizations",
        "description": "Lists the authorization behind each card purchase, newest first - including authorizations that have since settled. `status` says where each one is: the hold is open while it is `pending` or `authorized`. `settled_at` is null on every row that has not settled, open or released.\n\nA purchase that settled appears here and in `GET /cards/{card_id}/transactions`, so do not add the two lists together - read `item_object` on the page. In the sandbox a simulated purchase settles at once, so every row here is already `settled`. Refunds are not authorizations and are listed only on `/transactions`. A purchase OZAV declines is not listed on either route - it is on `GET /cards/{card_id}/declines`; an authorization OZAV approved and the card processor later declined, reversed or let expire stays here with `status` `declined` or `reversed` and `settled_at` null, unless a late capture settles it. Polling guidance is on `GET /cards/{card_id}/transactions`.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Card authorizations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardActivityPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/authorizations \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/authorizations\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/cards/{card_id}/authorizations\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/cards/{card_id}/declines": {
      "get": {
        "operationId": "listCardDeclines",
        "tags": [
          "Cards"
        ],
        "summary": "List card declines",
        "description": "Lists the purchases on this card that OZAV refused, newest first. Such a purchase leaves no hold and appears in neither `/authorizations` nor `/transactions`, so this is where to look when a cardholder asks why the card did not work, including when your endpoint missed the `card.declined` webhook. A hold the card processor refuses after OZAV approved it is not here: it stays on `/authorizations` as `declined`.\n\nEach row is the same refusal the `card.declined` event announced, and its `id` is that event's id. `fraud_category` is set only when `decline_code` is `fraud_rule`, and is `null` on refusals recorded before the field existed. A card that has never been seen through the v2 API has no declines here.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "card_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Card declines.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardDeclinePage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/cards/{card_id}/declines \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/cards/{card_id}/declines\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/cards/{card_id}/declines\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/card-surcharge-rules": {
      "get": {
        "operationId": "getCardSurchargeRules",
        "tags": [
          "Cards"
        ],
        "summary": "Get your card surcharge rules (sandbox only)",
        "description": "Returns the active version of your surcharge rules for card purchases: an amount you charge your cardholder on top of each purchase, debited from the same balance at authorization. The surcharge is yours; in the sandbox nothing is paid out, and how it is settled to you in production is defined with the card programme's go-live. `version: null` means you never published rules, and no surcharge is charged.\n\n**Scope: one set per organization, per environment.** The key selects only the environment (`test` or `live`), so two test keys of the same organization read and write the same set, and `environment` is returned with it. A rule cannot today target a specific customer or card, so two products under one organization cannot be priced differently; separate organizations have separate sets.\n\nA test key always works. A live key works once your organization is enabled in the live card programme, and live purchases are then charged by the live rules; before that, a live key receives `403 CARD_SURCHARGE_NOT_AVAILABLE`.",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The active rules, or `version: null` when none were published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardSurchargeRules"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/card-surcharge-rules \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/card-surcharge-rules\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/card-surcharge-rules\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "put": {
        "operationId": "putCardSurchargeRules",
        "tags": [
          "Cards"
        ],
        "summary": "Publish your card surcharge rules (sandbox only)",
        "description": "Publishes a new version of your surcharge rules. The body replaces the\nwhole set - send every rule you want to keep. Earlier versions are kept,\nand every purchase records the version that decided its rate\n(`client_surcharge.rule_version`), so your accounting can trace it.\n\n**Scope: one set per organization, per environment** - publishing\nreplaces the set both of your test keys read. A rule cannot today target\na specific customer or card.\n\n\n**Republishing the same set changes nothing.** If the body matches the\nactive version field for field, that version is returned and no new one\nis created, so a retry does not add noise to the version trail your\naccounting follows.\n\n\n**How a purchase is rated.** Rules are read in order and the first one\nwhose `when` matches wins. Inside `when`, every condition must match;\ninside each list, any value matches. If no rule matches,\n`default_rate_bps` applies - unless the issuer did not report the\npurchase currency (`presentment` absent), in which case\n`presentment_missing_rate_bps` applies. A currency condition never\nmatches an unknown currency.\n\n**Merchant names match as literal text**, case-insensitive and with\nrepeated spaces collapsed; `*` is a literal character, as in card\ndescriptors. `merchant_name_contains_any` is broad and\ncatches more than you may expect - `SHOP` also matches `WORKSHOP`. Use\n`merchant_name_starts_with_any` when the descriptor prefix is enough.\n\n**What the cardholder pays.** The surcharge is `rate_bps` of the\npurchase amount, rounded half-up to the cent, debited together with the\npurchase from the same balance: a balance that covers the purchase but\nnot the surcharge declines the purchase. Spending limits count only\nthe purchase. The rate is decided once, at authorization; when the\npurchase settles for a different amount, the surcharge is adjusted at\nthat same rate, and a reversed or expired authorization returns it.\nA refund the card processor links to its purchase returns the\nsurcharge proportionally, at the purchase's rate and never more than\nthe purchase charged; the refund row shows it as `client_surcharge`\nand it is taken back from what you are credited. A simulated refund\n(`simulate/purchase` with `is_refund`) is not linked to a purchase and\nreturns no surcharge.\n\nA test key always works. A live key works once your organization is enabled\nin the live card programme, and live purchases are then charged by the live\nrules; before that, a live key receives `403 CARD_SURCHARGE_NOT_AVAILABLE`.\n",
        "x-ozav-entitlement": "cards",
        "x-ozav-scopes": [
          "cards:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardSurchargeRulesPut"
              },
              "examples": {
                "by_currency_and_merchant": {
                  "summary": "A rate for BRL purchases, another for a merchant list, a default for the rest",
                  "value": {
                    "enabled": true,
                    "rules": [
                      {
                        "description": "Purchases billed in BRL",
                        "when": {
                          "presentment_currency_in": [
                            "BRL"
                          ]
                        },
                        "rate_bps": 150
                      },
                      {
                        "description": "Selected merchants outside BRL",
                        "when": {
                          "presentment_currency_not_in": [
                            "BRL"
                          ],
                          "merchant_name_starts_with_any": [
                            "EXAMPLE*ADS",
                            "EXAMPLE CLOUD"
                          ]
                        },
                        "rate_bps": 900
                      }
                    ],
                    "default_rate_bps": 400,
                    "presentment_missing_rate_bps": 400
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The version just published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardSurchargeRules"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/card-surcharge-rules \\\n  -X PUT \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"enabled\": true,\n    \"rules\": [\n      {\n        \"description\": \"Purchases billed in BRL\",\n        \"when\": {\n          \"presentment_currency_in\": [\n            \"BRL\"\n          ]\n        },\n        \"rate_bps\": 150\n      },\n      {\n        \"description\": \"Selected merchants outside BRL\",\n        \"when\": {\n          \"presentment_currency_not_in\": [\n            \"BRL\"\n          ],\n          \"merchant_name_starts_with_any\": [\n            \"EXAMPLE*ADS\",\n            \"EXAMPLE CLOUD\"\n          ]\n        },\n        \"rate_bps\": 900\n      }\n    ],\n    \"default_rate_bps\": 400,\n    \"presentment_missing_rate_bps\": 400\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/card-surcharge-rules\", {\n  method: \"PUT\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"enabled\": true,\n    \"rules\": [\n      {\n        \"description\": \"Purchases billed in BRL\",\n        \"when\": {\n          \"presentment_currency_in\": [\n            \"BRL\"\n          ]\n        },\n        \"rate_bps\": 150\n      },\n      {\n        \"description\": \"Selected merchants outside BRL\",\n        \"when\": {\n          \"presentment_currency_not_in\": [\n            \"BRL\"\n          ],\n          \"merchant_name_starts_with_any\": [\n            \"EXAMPLE*ADS\",\n            \"EXAMPLE CLOUD\"\n          ]\n        },\n        \"rate_bps\": 900\n      }\n    ],\n    \"default_rate_bps\": 400,\n    \"presentment_missing_rate_bps\": 400\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.put(\n    \"https://developers.ozav.io/v2/card-surcharge-rules\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"enabled\": True,\n        \"rules\": [\n            {\n                \"description\": \"Purchases billed in BRL\",\n                \"when\": {\n                    \"presentment_currency_in\": [\n                        \"BRL\",\n                    ],\n                },\n                \"rate_bps\": 150,\n            },\n            {\n                \"description\": \"Selected merchants outside BRL\",\n                \"when\": {\n                    \"presentment_currency_not_in\": [\n                        \"BRL\",\n                    ],\n                    \"merchant_name_starts_with_any\": [\n                        \"EXAMPLE*ADS\",\n                        \"EXAMPLE CLOUD\",\n                    ],\n                },\n                \"rate_bps\": 900,\n            },\n        ],\n        \"default_rate_bps\": 400,\n        \"presentment_missing_rate_bps\": 400,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets": {
      "post": {
        "operationId": "registerSelfCustodyWallet",
        "tags": [
          "Self-custody Wallets"
        ],
        "summary": "Register a self-custody wallet the customer already controls",
        "description": "Records an address your user already controls, so OZAV can read what it holds and build unsigned\ntransactions for it. Nothing is created: your user holds the key, and the address already exists.\n\nSend `owner_ref`, your own reference for the wallet's owner (your user id, for example), or\n`customer_id` for an OZAV customer, never both. With `owner_ref`, OZAV stores the address under that\nreference and no personal data.\n\n- **The address is checked against its network.** An address without the shape of an address on the\n  declared network is refused, which catches the common mistake of pasting one from another chain.\n  Recording an address does not prove your user holds its key.\n- **One address per owner and environment.** Registering again replaces the previous address, and\n  `replaced_previous_address` in the response says so.\n- `custody_model` accepts only `self_custody`. Wallets that OZAV holds come with the customer, not\n  from this route.\n- A wallet registered with `owner_ref` serves `POST /wallets/{wallet_id}/transaction-intents`. The\n  managed-custody routes answer `404` for its id.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "custody_model",
                  "chain",
                  "address"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "The customer this wallet belongs to (`ozav_cus_...`). Send this or `owner_ref`."
                  },
                  "owner_ref": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_.:@-]{1,128}$",
                    "description": "Your own reference for the wallet's owner (no OZAV customer). Send this or `customer_id`."
                  },
                  "custody_model": {
                    "type": "string",
                    "const": "self_custody",
                    "description": "Only `self_custody`. A managed wallet is not requested here."
                  },
                  "chain": {
                    "type": "string",
                    "description": "The network your customer's address lives on.\n\n**`tron` and `stellar` are refused with `422 CHAIN_HAS_NO_SELF_CUSTODY_CAPABILITY`.** They stay in this enum because they are still valid networks elsewhere in the API (managed custody serves both), but no endpoint can move a *self-custody* wallet on them today: `POST /transfers` only accepts a managed source, and `POST /wallets/{wallet_id}/transaction-intents` builds `{to, data, value, chain_id}`, which is an EVM transaction shape. Registering one would hand you a wallet with no way out, so it is refused - use a managed-custody wallet on those networks instead.\n\n`solana` **is** accepted even though it has no transaction-intents support: a self-custody Solana wallet is served by the yield line, so it is not a dead end.",
                    "enum": [
                      "ethereum",
                      "base",
                      "polygon",
                      "arbitrum",
                      "optimism",
                      "avalanche_c_chain",
                      "solana",
                      "tron",
                      "stellar"
                    ]
                  },
                  "address": {
                    "type": "string",
                    "description": "The address your customer controls. Sent back exactly as supplied - OZAV does not normalise case, because on EVM the mixed case is the checksum that catches a mistyped digit."
                  }
                }
              },
              "examples": {
                "evm": {
                  "summary": "Register a Base address",
                  "value": {
                    "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "custody_model": "self_custody",
                    "chain": "base",
                    "address": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The recorded self-custody wallet.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "object",
                    "customer_id",
                    "custody_model",
                    "network",
                    "address",
                    "replaced_previous_address"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Opaque wallet identifier, prefixed `ozav_wal_`. This is the id to pass to `GET /wallets/{wallet_id}/balances` and every other wallet route. It is the same id `GET /wallets` returns for this wallet, so you do not need to list back after creating. Passing a `ozav_cus_` id to a wallet route answers `404 WALLET_NOT_FOUND`."
                    },
                    "object": {
                      "type": "string",
                      "const": "wallet"
                    },
                    "customer_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The customer (`ozav_cus_...`), or `null` for a wallet registered with `owner_ref`."
                    },
                    "owner_ref": {
                      "type": "string",
                      "description": "Present only for a wallet registered with `owner_ref`."
                    },
                    "custody_model": {
                      "type": "string",
                      "const": "self_custody"
                    },
                    "network": {
                      "type": "string"
                    },
                    "address": {
                      "type": "string"
                    },
                    "replaced_previous_address": {
                      "type": "boolean",
                      "description": "`true` when a different address was on file before. Stop using the old one. Re-registering the same address is idempotent and reports `false`."
                    }
                  }
                },
                "examples": {
                  "recorded": {
                    "summary": "First address recorded for this customer",
                    "value": {
                      "id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0W2",
                      "object": "wallet",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "custody_model": "self_custody",
                      "network": "base",
                      "address": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8",
                      "replaced_previous_address": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Well-formed but not recordable. `ADDRESS_NOT_VALID_FOR_CHAIN` - the address does not have the shape of an address on the declared network, which is almost always an address pasted from another chain.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/DependencyUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"custody_model\": \"self_custody\",\n    \"chain\": \"base\",\n    \"address\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"custody_model\": \"self_custody\",\n    \"chain\": \"base\",\n    \"address\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/wallets\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"customer_id\": \"ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"custody_model\": \"self_custody\",\n        \"chain\": \"base\",\n        \"address\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listWallets",
        "tags": [
          "Custodial Wallets"
        ],
        "summary": "List wallets",
        "description": "Lists wallets with the standard cursor pagination; filter with the\n`customer_id` query parameter. Each wallet reports its `custody_model`.\nReads are tenant-scoped. Returns a `Page` of `Wallet`.\nSandbox verification simulation creates a simulated managed wallet\nprojection for approved customers when no wallet or self-custody address\nalready exists. Live managed wallet provisioning, on-chain addresses,\nand managed sends remain gated. Use `GET /accounts?customer_id=...`\nfor customer BRL/Pix account instructions.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Filter to a single customer by its opaque `ozav_cus_` id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated wallets.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Wallet"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "page": {
                    "summary": "One page of wallets",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "wallet",
                          "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "custody_model": "managed",
                          "status": "active",
                          "created_at": "2026-07-12T18:00:00Z"
                        }
                      ],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/wallets\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}": {
      "get": {
        "operationId": "getWallet",
        "tags": [
          "Self-custody Wallets"
        ],
        "summary": "Retrieve a wallet",
        "description": "Returns one wallet by its `ozav_wal_` id, with its `custody_model` and `status`. A wallet registered\nwith `owner_ref` comes back with `owner_ref`, `network` and `address` instead of `customer_id`.\n\nA wallet outside your organization answers `404`. A customer's wallet needs the `custody` product; a\nwallet registered with `owner_ref` needs `self_custody`.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The wallet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Wallet"
                },
                "examples": {
                  "managed": {
                    "summary": "A managed wallet",
                    "value": {
                      "id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "wallet",
                      "customer_id": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "custody_model": "managed",
                      "status": "active",
                      "created_at": "2026-07-12T18:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}/deposit-addresses": {
      "get": {
        "operationId": "listWalletDepositAddresses",
        "tags": [
          "Custodial Wallets"
        ],
        "summary": "List the deposit addresses of a wallet",
        "description": "The on-chain addresses to send funds to so they credit this wallet. Tenant-scoped: a `wallet_id` outside the caller's tenant answers 404 and never leaks existence.\n\nRead `memo_required` before you build a deposit instruction. Some networks attribute a deposit by memo rather than by address: on those, every customer shares one account and the memo is the only thing that routes the funds to the right one. A deposit sent without it, or with the wrong one, cannot be attributed. Where `memo_required` is true the `memo` field is always present; where it is false, `memo` is null and must not be sent.\n\nA wallet in self-custody returns an empty `data` array: the funds are the end user's, on their own address, and OZAV has no address to offer.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One deposit address per supported network.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DepositAddress"
                      }
                    }
                  }
                },
                "examples": {
                  "deposit_addresses": {
                    "summary": "An address-per-user network and a memo-based one",
                    "value": {
                      "data": [
                        {
                          "object": "deposit_address",
                          "network": "ethereum",
                          "address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
                          "memo": null,
                          "memo_required": false,
                          "created_at": "2026-08-12T10:00:00Z"
                        },
                        {
                          "object": "deposit_address",
                          "network": "stellar",
                          "address": "GBTUMXPEAPKTFIZAI47GKGUWMHWUONVX6PXGBPLSCC6WM3MYDPHQ3VSL",
                          "memo": "mA1b2C3d4E5f6G7h8I9j0K1l2M3n",
                          "memo_required": true,
                          "created_at": "2026-08-12T10:00:00Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deposit-addresses \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deposit-addresses\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/deposit-addresses\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}/balances": {
      "get": {
        "operationId": "listWalletBalances",
        "tags": [
          "Custodial Wallets"
        ],
        "summary": "List balances of a wallet",
        "description": "The wallet's available balance per currency, from OZAV's ledger: posted credits minus posted debits,\npending debits and card spend, as a decimal string at the currency's precision. It is never more than\nthe wallet holds on chain. One balance per currency across networks, so `network` is omitted. This is the balance to reconcile against; what sits at each address on chain is\n`GET /wallets/{wallet_id}/onchain-balances`.\n\nA self-custody wallet returns an empty `data` array: OZAV holds none of its funds. A wallet outside your\norganization answers `404`. With a `test` key the balance is the sandbox one.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Balances by currency (and network where applicable).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Balance"
                      }
                    }
                  }
                },
                "examples": {
                  "balances": {
                    "summary": "Balances by currency and network",
                    "value": {
                      "data": [
                        {
                          "amount": "1500.000000",
                          "currency": "USDC",
                          "network": "base"
                        },
                        {
                          "amount": "250.500000",
                          "currency": "USDT",
                          "network": "tron"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/balances \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/balances\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/balances\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}/onchain-balances": {
      "get": {
        "operationId": "listWalletOnchainBalances",
        "tags": [
          "Self-custody Wallets"
        ],
        "summary": "List on-chain balances of a wallet, per network",
        "description": "What is at the wallet's address on chain right now, per network and currency.\n\n- **Registered with `owner_ref`:** OZAV reads the address your user registered, on the network it was\n  registered on, for each currency OZAV supports there.\n- **Held by OZAV:** OZAV reads each deposit address of the wallet. This is not the ledger balance of\n  `GET /wallets/{wallet_id}/balances`, which is the available balance per currency across networks\n  (posted credits minus posted debits minus pending debits): this read does not subtract transfers in\n  flight, does not wait for finality, and includes a deposit that arrived and is not credited yet, so\n  the two do not sum to the same number. Use it to see what can leave on a given network; use\n  `/balances` to reconcile.\n- **Self-custody wallet of an OZAV customer:** OZAV holds no address for it, and `data` is empty.\n\nFor a wallet held by OZAV, only the networks offered to your organization appear, the same set\n`/deposit-addresses` serves (see `x-ozav-network-availability` on `Network`), and a network where\ncustomers share one deposit address with a memo is left out, because that balance is not\nattributable to one customer. A deposit on a network no longer offered is still credited and shows in\n`/balances`.\n\n`amount` is `null` when the balance could not be read, never `0`, and `unreadable_reason` says why.\nThe other networks in the response are unaffected. A wallet outside your organization answers `404`.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "On-chain balances, one entry per (network, currency).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OnchainBalance"
                      }
                    }
                  }
                },
                "examples": {
                  "onchain_balances": {
                    "summary": "Two networks read, one unreadable",
                    "value": {
                      "data": [
                        {
                          "object": "onchain_balance",
                          "network": "tron",
                          "currency": "USDT",
                          "address": "TLkDwwyqdTXjaUnCKTTYJzxtKMh8R492EF",
                          "amount": "5.750742"
                        },
                        {
                          "object": "onchain_balance",
                          "network": "ethereum",
                          "currency": "USDT",
                          "address": "0x761c6FdE080a2A386834527a0Eb4eDbDc7f6B378",
                          "amount": "2.2"
                        },
                        {
                          "object": "onchain_balance",
                          "network": "polygon",
                          "currency": "USDC",
                          "address": "0x761c6FdE080a2A386834527a0Eb4eDbDc7f6B378",
                          "amount": null,
                          "unreadable_reason": "leitura de saldo passou de 4000ms"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/onchain-balances \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/onchain-balances\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/onchain-balances\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}/transaction-intents": {
      "post": {
        "operationId": "createTransactionIntent",
        "tags": [
          "Self-custody Wallets"
        ],
        "summary": "Build an unsigned transaction for a self-custody wallet",
        "description": "Builds an unsigned transaction for a `self_custody` wallet. Your user signs and broadcasts it; OZAV\nnever holds the key and never signs.\n\n- The token contract comes from OZAV's verified registry, and the amount is converted to the token's\n  smallest unit without floating point.\n- What cannot be built exactly is refused with `422` instead of approximated: a network whose\n  transaction is not `{to, data, value, chain_id}`, an asset not verified on that network, or an\n  amount smaller than one unit of the token.\n- A wallet OZAV holds answers `422 WALLET_NOT_SELF_CUSTODY`. Wallets registered with `owner_ref` are\n  accepted.\n\nSend an `Idempotency-Key` to retry safely: the same key and body return the same transaction intent\nwith `Idempotent-Replayed: true`, and on OZAV Services the retry is not charged again.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "live",
        "x-ozav-services": true,
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "intent"
                ],
                "properties": {
                  "intent": {
                    "type": "string",
                    "description": "The transaction to build - a send from this wallet:\n- `withdraw` - send funds out to an external destination.\n- `transfer` - move funds to another address.\n- `deposit` - rejected: inbound deposits are signed by the external payer, not built here.\n",
                    "enum": [
                      "deposit",
                      "withdraw",
                      "transfer"
                    ]
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "amount": {
                    "$ref": "#/components/schemas/DecimalAmount"
                  },
                  "destination": {
                    "type": "string",
                    "description": "The recipient address the send goes to (must be valid for the wallet's chain)."
                  }
                }
              },
              "examples": {
                "transfer": {
                  "summary": "Build an unsigned USDC transfer",
                  "value": {
                    "intent": "transfer",
                    "currency": "USDC",
                    "amount": "250.000000",
                    "destination": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Unsigned transaction payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionIntent"
                },
                "examples": {
                  "unsigned": {
                    "summary": "An unsigned USDC transfer for the client to sign",
                    "value": {
                      "id": "ozav_txi_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "transaction_intent",
                      "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "intent": "transfer",
                      "unsigned_transaction": {
                        "to": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8",
                        "data": "0xa9059cbb0000000000000000000000001d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8000000000000000000000000000000000000000000000000000000000ee6b280",
                        "value": "0",
                        "chain_id": 8453
                      },
                      "expires_at": "2026-07-12T18:05:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/transaction-intents \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"intent\": \"transfer\",\n    \"currency\": \"USDC\",\n    \"amount\": \"250.000000\",\n    \"destination\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/transaction-intents\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"intent\": \"transfer\",\n    \"currency\": \"USDC\",\n    \"amount\": \"250.000000\",\n    \"destination\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/transaction-intents\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"intent\": \"transfer\",\n        \"currency\": \"USDC\",\n        \"amount\": \"250.000000\",\n        \"destination\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/approvals": {
      "get": {
        "operationId": "listApprovals",
        "tags": [
          "Deprecated"
        ],
        "summary": "List approvals",
        "deprecated": true,
        "description": "**Deprecated.** OZAV sets the rules of managed-custody wallets, and no\nsend waits on approver keys: this list has no approvals to show. It stays\navailable to the integrations that read it until the date in OZAV's\nnotice to them.\n\nThe approvals of your organization, newest first. `?status=` filters by\nstatus.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "approvals:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "rejected",
                "expired",
                "executed",
                "failed"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of approvals. It has none to show.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Approval"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                },
                "examples": {
                  "empty": {
                    "summary": "No approvals",
                    "value": {
                      "data": [],
                      "has_more": false,
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/approvals \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/approvals\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/approvals\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}/addresses": {
      "get": {
        "operationId": "listWalletAddresses",
        "tags": [
          "Deprecated"
        ],
        "summary": "List the address book of a wallet",
        "deprecated": true,
        "description": "**Deprecated.** OZAV sets the destinations of managed-custody wallets.\nThis read stays available to the integrations that use it until the date\nin OZAV's notice to them.\n\nThe destinations of this managed wallet with their labels: what is\n`allowed` (leaves without human review), what is still `pending_review`,\nand what was `rejected`. It is the same list as `destinations.allowed` on\nthe policy, plus the pending requests.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The address book.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WalletAddress"
                      }
                    }
                  }
                },
                "examples": {
                  "book": {
                    "summary": "One allowed destination and one awaiting review",
                    "value": {
                      "data": [
                        {
                          "object": "wallet_address",
                          "network": "ethereum",
                          "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
                          "label": "cold vault",
                          "status": "allowed",
                          "added_at": "2026-08-01T00:00:00Z",
                          "requested_at": "2026-08-01T00:00:00Z"
                        },
                        {
                          "object": "wallet_address",
                          "network": "solana",
                          "address": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
                          "label": "treasury exchange",
                          "status": "pending_review",
                          "added_at": null,
                          "requested_at": "2026-09-09T12:00:00Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/addresses \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/addresses\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/addresses\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}/policy": {
      "get": {
        "operationId": "getWalletPolicy",
        "tags": [
          "Deprecated"
        ],
        "summary": "Read the policy of a managed wallet",
        "deprecated": true,
        "description": "**Deprecated.** OZAV sets the rules of managed-custody wallets. This route\nstays available to the integrations that read it until the date in OZAV's\nnotice to them.\n\nWhat OZAV applies before signing a send from this `managed` wallet, as one\nobject: the destinations that leave without human review, the limits in\nforce and where they come from, and the limit requests still being decided.\n`approvals.mode` is always `ozav_review`, and `delay` and `cosigner` are\nalways off.\n\n**Read `limits.source` before you read the numbers.** `wallet` means a\nsigned per-wallet record is in force; these numbers come from the newest\none. If more than one is active, OZAV applies the smallest of each\nlimit, and the automatic amount never goes above the per-send limit. `organization` and `unconfigured` mean no per-wallet record\nexists yet: the numbers shown are your organization's defaults (or none),\nand until a record is signed every send from this wallet is checked\nagainst OZAV's house ceiling of USD 100,000 per send, per rolling 24\nhours, and for destinations outside `destinations.allowed`.\n\nUp to the automatic amount (`limits.auto_approve_below_usd` when `source`\nis `wallet`), a clean send to any destination leaves on its own. Above it,\nonly a destination in `destinations.allowed` or an OZAV address receives;\nany other is refused with `422 DESTINATION_NOT_ALLOWLISTED` (the message\nsays the amount) and nothing moves. Tenant-scoped: a `wallet_id`\noutside the caller's tenant answers 404 and never leaks existence. A\n`self_custody` wallet has no policy here (its owner holds the key) and\nanswers `422 WALLET_NOT_MANAGED`.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The policy in force, and the limit requests still being decided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletPolicy"
                },
                "examples": {
                  "policy": {
                    "summary": "A wallet with a signed limit record and one limit request awaiting review",
                    "value": {
                      "object": "wallet_policy",
                      "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "destinations": {
                        "mode": "allowlist",
                        "allowed": [
                          {
                            "network": "ethereum",
                            "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
                            "added_at": "2026-08-01T00:00:00Z"
                          },
                          {
                            "network": "tron",
                            "address": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
                            "added_at": "2026-08-02T00:00:00Z"
                          }
                        ]
                      },
                      "limits": {
                        "per_transaction_usd": 50000,
                        "daily_usd": 200000,
                        "auto_approve_below_usd": 1000,
                        "source": "wallet",
                        "valid_until": "2027-02-01T00:00:00Z"
                      },
                      "approvals": {
                        "mode": "ozav_review"
                      },
                      "pending_changes": [
                        {
                          "id": "6f1d2c3b-4a5e-4f60-9a1b-2c3d4e5f6a7b",
                          "kind": "limits.update",
                          "status": "pending_review",
                          "requested_at": "2026-09-09T12:00:00Z",
                          "decided_at": null,
                          "per_transaction_usd": 50000,
                          "daily_usd": 500000,
                          "auto_approve_below_usd": 1000
                        }
                      ],
                      "updated_at": "2026-08-02T00:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/policy \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/policy\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/policy\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "patch": {
        "operationId": "proposeWalletPolicyChange",
        "tags": [
          "Deprecated"
        ],
        "summary": "Request a limit change for a managed wallet",
        "deprecated": true,
        "description": "**Deprecated.** OZAV sets the rules of managed-custody wallets. This route\nstays available, for limit requests only, to the integrations that use it\nuntil the date in OZAV's notice to them.\n\n`limits` is the only field, and it is a request: it enters review by OZAV\nand nothing changes on this call. It appears at once in `pending_changes`,\nand when decided you receive `wallet_policy.updated`. An approved change\ncomes into force minutes later, when its signed record is issued - confirm\nit by reading `limits.valid_until`.\n\nMissing limit fields inherit the values in force; when nothing is in force\nyet, both `per_transaction_usd` and `daily_usd` are required. A limit\nchange needs a provisioned custody vault (`409 POLICY_LIMITS_REQUIRE_VAULT`\nbefore that). A request carrying `destinations`, `approvals`, `delay` or\n`cosigner` answers `422 WALLET_RULE_SET_BY_OZAV`, with `param` naming the\nfield, and nothing in it is applied.\n\n`Idempotency-Key` is **required**: a request must not be enqueued twice by\na retry. In the sandbox limits are read-only\n(`422 POLICY_SANDBOX_READ_ONLY`); use a live key.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required idempotency key (16-256 characters of `[A-Za-z0-9_-]`) - a limit request must be safely retryable.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WalletPolicyChangeRequest"
              },
              "examples": {
                "raise_daily_limit": {
                  "summary": "Raise the daily limit, keeping the other numbers in force",
                  "value": {
                    "limits": {
                      "daily_usd": 500000
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The limit request is queued for review. The body is the policy as it stands now - the request appears in `pending_changes`; nothing in force has moved yet.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletPolicy"
                },
                "examples": {
                  "queued": {
                    "summary": "The limit request is awaiting review; the limits in force are unchanged",
                    "value": {
                      "object": "wallet_policy",
                      "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "destinations": {
                        "mode": "allowlist",
                        "allowed": [
                          {
                            "network": "ethereum",
                            "address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
                            "added_at": "2026-08-01T00:00:00Z"
                          }
                        ]
                      },
                      "limits": {
                        "per_transaction_usd": 50000,
                        "daily_usd": 200000,
                        "auto_approve_below_usd": 1000,
                        "source": "wallet",
                        "valid_until": "2027-02-01T00:00:00Z"
                      },
                      "approvals": {
                        "mode": "ozav_review"
                      },
                      "pending_changes": [
                        {
                          "id": "7a2e3d4c-5b6f-4071-8b2c-3d4e5f6a7b8c",
                          "kind": "limits.update",
                          "status": "pending_review",
                          "requested_at": "2026-09-09T12:00:00Z",
                          "decided_at": null,
                          "per_transaction_usd": 50000,
                          "daily_usd": 500000,
                          "auto_approve_below_usd": 1000
                        }
                      ],
                      "updated_at": "2026-08-01T00:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`POLICY_LIMITS_REQUIRE_VAULT` - the wallet has no custody vault yet, so it has no per-wallet limits to change; `POLICY_LIMITS_CHANGE_PENDING` - a limits change for this wallet that you cannot replace is already open (one OZAV opened, or one already approved). Your own pending limits request is replaced by the new one, and no longer shows in `pending_changes`; or `IDEMPOTENCY_KEY_REUSED`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "`WALLET_RULE_SET_BY_OZAV`, `POLICY_LIMITS_INCOMPLETE`, `POLICY_LIMITS_INVALID`, `WALLET_NOT_MANAGED` or `POLICY_SANDBOX_READ_ONLY`. `param` names the field.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/policy \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"limits\": {\n      \"daily_usd\": 500000\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/policy\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"limits\": {\n      \"daily_usd\": 500000\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/policy\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"limits\": {\n            \"daily_usd\": 500000,\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/wallets/{wallet_id}/transfers": {
      "post": {
        "operationId": "createManagedTransfer",
        "tags": [
          "Custodial Wallets"
        ],
        "summary": "Send from a managed-custody wallet (OZAV signs)",
        "description": "For `managed` wallets only, where OZAV custodies the key. **OZAV signs and\nbroadcasts this send.** It is the inverse of `transaction-intents`, which\nis for `self_custody` wallets, where the client signs.\n\nThis is a real, irreversible money movement, so `Idempotency-Key` is\n**required** - a request without one is rejected rather than risking a\nsecond broadcast. Treat a timeout as unknown, never as failed: retry with\nthe same key, which replays the stored result instead of sending again.\n\nThe send is refused before anything is signed when the wallet's asset does\nnot match `currency`, when the destination is not valid for the wallet's\nnetwork, or when the corridor is not enabled for your organization.\nNothing is broadcast on a refusal.\n\n**The same move is also available on `POST /transfers`**, with a `wallet`\nsource and a `wallet` or `crypto_address` destination. Both routes run\nthe same send and apply the same two controls (managed wallets only;\nTravel Rule on any send that leaves custody), so the choice is about which\nresource you want back, not about what happens on-chain:\n\n- Use **this route** when the send needs a destination `memo`. Networks\n  that carry one (Stellar) need it for exchange deposits - an exchange\n  credits by memo, and a deposit without one arrives unattributed.\n  `POST /transfers` has no `memo` field.\n- Use **`POST /transfers`** when you want a `Transfer` resource, so the\n  send appears in `GET /transfers` alongside your on-ramp, off-ramp and\n  FX movements and is reconciled through the same status model.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "wallet_id",
            "in": "path",
            "required": true,
            "description": "The opaque wallet id (`ozav_wal_...`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Required idempotency key (16-256 characters of `[A-Za-z0-9_-]`) - a managed transfer is a money movement and must be safely retryable.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "currency",
                  "amount",
                  "destination"
                ],
                "properties": {
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "amount": {
                    "$ref": "#/components/schemas/DecimalAmount"
                  },
                  "destination": {
                    "type": "string",
                    "description": "The recipient - a client-owned wallet id (ozav_wal_) or an on-chain address valid for the wallet's chain."
                  },
                  "token_id": {
                    "type": "string",
                    "pattern": "^ozav_tok_[0-9A-Za-z]{16,}$",
                    "deprecated": true,
                    "description": "Retired. Client token registration is no longer offered, so no token id exists to send: a request carrying `token_id` answers `404 CLIENT_TOKEN_NOT_FOUND`. Send the assets OZAV supports in this wallet with `currency`.",
                    "example": "ozav_tok_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                  },
                  "nft_collection_id": {
                    "type": "string",
                    "pattern": "^ozav_nft_[0-9A-Za-z]{16,}$",
                    "deprecated": true,
                    "description": "Retired. NFT collection registration is no longer offered, so no collection id exists to send from: a request carrying `nft_collection_id` answers `404 NFT_COLLECTION_NOT_FOUND`.",
                    "example": "ozav_nft_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
                  },
                  "nft_token_id": {
                    "type": "string",
                    "pattern": "^(0|[1-9][0-9]{0,77})$",
                    "deprecated": true,
                    "description": "Retired with `nft_collection_id`.",
                    "example": "1234567890123456789012345"
                  },
                  "nft_quantity": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]{0,29}$",
                    "deprecated": true,
                    "description": "Retired with `nft_collection_id`.",
                    "example": "1"
                  },
                  "chain": {
                    "type": "string",
                    "description": "Optional explicit chain for the send. When present, both the source and the destination are resolved on this chain only, and a mismatch fails before anything is signed or broadcast. Supply it whenever the wallet holds the same asset on more than one chain - omitting it lets OZAV choose.",
                    "example": "stellar"
                  },
                  "memo": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 28,
                    "description": "Destination memo/tag, for networks whose transaction format carries one (Stellar). **On a memo-based network the recipient credits by memo: a send without it arrives and is attributed to nobody.** Every major exchange deposit address on Stellar is shared across its users, so sending there without the memo the exchange gave you loses the deposit. Read `memo_required` on `GET /wallets/{wallet_id}/deposit-addresses` for the receiving side of the same rule. 28 characters is the Stellar `memo_text` ceiling; a longer value is rejected with `400` naming the field, never truncated.",
                    "example": "2importmemo9"
                  },
                  "travel_rule": {
                    "$ref": "#/components/schemas/TravelRule"
                  }
                }
              },
              "examples": {
                "send": {
                  "summary": "Send 250 USDT from a managed wallet",
                  "value": {
                    "currency": "USDT",
                    "amount": "250.000000",
                    "destination": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8"
                  }
                },
                "send_memo_network": {
                  "summary": "Send on a memo-based network (Stellar) - the memo is what attributes it",
                  "value": {
                    "currency": "USDC",
                    "amount": "250.000000",
                    "chain": "stellar",
                    "destination": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
                    "memo": "2importmemo9"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The settled managed transfer.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "object": {
                      "type": "string",
                      "enum": [
                        "wallet_transfer"
                      ]
                    },
                    "wallet_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed",
                        "processing"
                      ]
                    },
                    "amount": {
                      "$ref": "#/components/schemas/DecimalAmount"
                    },
                    "currency": {
                      "$ref": "#/components/schemas/Currency"
                    },
                    "destination": {
                      "type": "string"
                    },
                    "transaction_hash": {
                      "type": "string"
                    },
                    "settled_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                },
                "examples": {
                  "settled": {
                    "summary": "A settled managed USDT transfer",
                    "value": {
                      "id": "ozav_wtr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "wallet_transfer",
                      "wallet_id": "ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "status": "completed",
                      "amount": "250.000000",
                      "currency": "USDT",
                      "destination": "0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8",
                      "transaction_hash": "0x6c984fe16f15aa9865e7ca950c5038542e5789c8bd6dfc2fd95f649514a88b47",
                      "settled_at": "2026-07-17T00:39:47Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/transfers \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"currency\": \"USDT\",\n    \"amount\": \"250.000000\",\n    \"destination\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/transfers\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"currency\": \"USDT\",\n    \"amount\": \"250.000000\",\n    \"destination\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/wallets/ozav_wal_01J8Z9K2M3N4P5Q6R7S8T9U0V1/transfers\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"currency\": \"USDT\",\n        \"amount\": \"250.000000\",\n        \"destination\": \"0x1d4c9ae2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/statements": {
      "get": {
        "operationId": "listStatements",
        "tags": [
          "Statements"
        ],
        "summary": "List service statements",
        "description": "Your organization's monthly service statements, newest first, up to the last 36 months. Each one is what OZAV charges you, or credits you, for that month: volume, transactions, active users, your share of spread, and the amount due or credited. A statement covers live activity, so a test key gets an empty list.\n\nThe scope `billing:read` is granted to every key created from now on; a key created before it needs to be replaced to read statements.",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "billing:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The statements.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatementList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/statements \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/statements\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/statements\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/statements/{statement_id}": {
      "get": {
        "operationId": "getStatement",
        "tags": [
          "Statements"
        ],
        "summary": "Get a service statement",
        "description": "One monthly statement with its itemized lines. `statement_id` is the statement's `id` from the list (`ozav_stmt_` followed by the year and month, e.g. `ozav_stmt_202609`).",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "billing:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "statement_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The statement.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatementDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/statements/{statement_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/statements/{statement_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/statements/{statement_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/api-keys": {
      "post": {
        "operationId": "createApiKey",
        "tags": [
          "API Keys"
        ],
        "summary": "Create an API key (self-serve)",
        "description": "Creates a new API key scoped to a subset of your entitlements. The full\n`secret` is returned exactly once, in this response - store it securely,\nas it cannot be retrieved again.\n\n**Give it either `role` or `scopes` - exactly one.**\n\n- **`role`** is the shortcut: `admin`, `operator` or `viewer`. Use it to\n  separate powers between the people and systems on your side without\n  picking 32 scopes by hand. A role is a **subset of what the minting key\n  already holds** - never a way to get more - so a key minted with\n  `role: admin` by a narrow key is still narrow.\n- **`scopes`** is the explicit list, intersected with the organization's\n  entitlements at request time; an empty set is rejected.\n\nSending both, or neither, is a `400`: a permission object should not have\nan implicit default, and there is no sensible rule for which one wins.\n",
        "x-ozav-entitlement": "developer",
        "x-ozav-scopes": [
          "keys:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "oneOf": [
                  {
                    "required": [
                      "role"
                    ],
                    "not": {
                      "required": [
                        "scopes"
                      ]
                    }
                  },
                  {
                    "required": [
                      "scopes"
                    ],
                    "not": {
                      "required": [
                        "role"
                      ]
                    }
                  }
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "A human-readable label for the key, to identify it later."
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "admin",
                      "operator",
                      "viewer"
                    ],
                    "description": "The key's role - a template over the scopes the minting key\nalready holds. Mutually exclusive with `scopes`.\n\n- `admin` - everything the minting key holds.\n- `operator` - everything except `keys:write` and\n  `agent_grants:write`, so the key cannot mint or revoke other\n  keys, nor grant an agent mandate. It keeps `keys:read`, so\n  it still sees which keys exist.\n- `viewer` - read scopes only.\n\nA role is a **subset** of the minting key's scopes: it never\ngrants more than the caller already had.\n\nThe role is stored and echoed back on the key, and a rotated\nkey keeps it. It is a label and a mint-time template - it is\n**not** part of the permission decision, which stays\n`entitlements ∩ scopes`.\n"
                  },
                  "environment": {
                    "type": "string",
                    "description": "Which environment the new key is for - `test` (sandbox) or\n`live` (production). The prefix of the returned key reflects this.\n",
                    "enum": [
                      "test",
                      "live"
                    ]
                  },
                  "scopes": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "enum": [
                        "customers:read",
                        "customers:write",
                        "accounts:read",
                        "accounts:write",
                        "quotes:read",
                        "quotes:write",
                        "transfers:read",
                        "transfers:write",
                        "destinations:read",
                        "destinations:write",
                        "wallets:read",
                        "wallets:write",
                        "screenings:read",
                        "screenings:write",
                        "lists:read",
                        "lists:write",
                        "decisions:read",
                        "decisions:write",
                        "verifications:read",
                        "verifications:write",
                        "cards:read",
                        "cards:write",
                        "webhooks:read",
                        "webhooks:write",
                        "events:read",
                        "keys:read",
                        "keys:write",
                        "transactions:read",
                        "yield:read",
                        "yield:write",
                        "yield:apply",
                        "swap:read",
                        "network_transfers:read",
                        "network_transfers:write",
                        "approvals:read",
                        "approvals:write",
                        "evidence:read",
                        "agents:read",
                        "agents:write",
                        "agent_payments:write",
                        "agent_grants:write",
                        "billing:read"
                      ]
                    },
                    "description": "Scopes are intersected with the organization's entitlements at request time; an\nempty scope set is invalid. This is a **closed set**: a value outside the enum\nabove is **rejected** with `400 INVALID_SCOPE`, and the error lists every valid\nscope - a typo fails loudly at mint time rather than quietly minting a key with\na narrower scope set than you asked for. You can only grant scopes the minting\nkey itself holds (`403 SCOPE_NOT_HELD` otherwise).\n"
                  }
                }
              },
              "examples": {
                "operator_role": {
                  "summary": "An operator key, without listing scopes by hand",
                  "value": {
                    "name": "payments operator",
                    "environment": "live",
                    "role": "operator"
                  }
                },
                "live_key": {
                  "summary": "A live key scoped to move money",
                  "value": {
                    "name": "server-side production key",
                    "environment": "live",
                    "scopes": [
                      "quotes:write",
                      "transfers:write",
                      "transfers:read",
                      "customers:read"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created; the secret is returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyWithSecret"
                },
                "examples": {
                  "created": {
                    "summary": "Secret returned once - store it securely",
                    "value": {
                      "id": "ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                      "object": "api_key",
                      "name": "server-side production key",
                      "key_prefix": "ozav_sk_live_a1b2",
                      "environment": "live",
                      "scopes": [
                        "quotes:write",
                        "transfers:write",
                        "transfers:read",
                        "customers:read"
                      ],
                      "created_at": "2026-07-12T18:00:00Z",
                      "secret": "ozav_sk_live_a1b2c3d4e5f60718293a4b5c6d7e8f90"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/api-keys \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"payments operator\",\n    \"environment\": \"live\",\n    \"role\": \"operator\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/api-keys\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"name\": \"payments operator\",\n    \"environment\": \"live\",\n    \"role\": \"operator\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/api-keys\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"name\": \"payments operator\",\n        \"environment\": \"live\",\n        \"role\": \"operator\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listApiKeys",
        "tags": [
          "API Keys"
        ],
        "summary": "List API keys (prefixes only)",
        "description": "Lists your API keys with their non-secret `key_prefix`, scopes, and\nenvironment. Secrets are never returned here - a secret is shown only at\ncreation and rotation. Live.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "keys:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "Keys (no secrets).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    }
                  }
                },
                "examples": {
                  "keys": {
                    "summary": "Your keys, prefixes only",
                    "value": {
                      "data": [
                        {
                          "id": "ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                          "object": "api_key",
                          "name": "server-side production key",
                          "key_prefix": "ozav_sk_live_a1b2",
                          "environment": "live",
                          "scopes": [
                            "quotes:write",
                            "transfers:write",
                            "transfers:read",
                            "customers:read"
                          ],
                          "last_used_at": "2026-07-12T17:59:00Z",
                          "created_at": "2026-07-12T18:00:00Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/api-keys \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/api-keys\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/api-keys\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/api-keys/{api_key_id}": {
      "delete": {
        "operationId": "revokeApiKey",
        "tags": [
          "API Keys"
        ],
        "summary": "Revoke an API key",
        "description": "Revokes an API key immediately; subsequent requests presenting that key\nfail authentication. Returns `204 No Content`. Irreversible - mint a new\nkey to replace a revoked one. Live.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "keys:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "api_key_id",
            "in": "path",
            "required": true,
            "description": "The opaque API-key id (`ozav_key_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/api-keys/ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -X DELETE \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/api-keys/ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"DELETE\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.delete(\n    \"https://developers.ozav.io/v2/api-keys/ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/api-keys/{api_key_id}/rotate": {
      "post": {
        "operationId": "rotateApiKey",
        "tags": [
          "API Keys"
        ],
        "summary": "Rotate an API key with an overlap window",
        "description": "Mints a successor key with identical scopes. The previous secret\nremains valid for the overlap window (default 24 hours, maximum 7\ndays), then is revoked automatically. Live.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "keys:write"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "api_key_id",
            "in": "path",
            "required": true,
            "description": "The opaque API-key id (`ozav_key_...`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "overlap_hours": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 168,
                    "default": 24,
                    "description": "How long the previous secret stays valid after rotation, in hours (0-168; default 24). It is revoked automatically when the window elapses."
                  }
                }
              },
              "examples": {
                "overlap": {
                  "summary": "48-hour overlap window",
                  "value": {
                    "overlap_hours": 48
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successor key; secret returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyWithSecret"
                },
                "examples": {
                  "rotated": {
                    "summary": "Successor key - store the new secret; the old one stays valid for the overlap window",
                    "value": {
                      "id": "ozav_key_02J8Z9K2M3N4P5Q6R7S8T9U0V2",
                      "object": "api_key",
                      "name": "server-side production key",
                      "key_prefix": "ozav_sk_live_c3d4",
                      "environment": "live",
                      "scopes": [
                        "quotes:write",
                        "transfers:write",
                        "transfers:read",
                        "customers:read"
                      ],
                      "created_at": "2026-07-12T18:20:00Z",
                      "secret": "ozav_sk_live_c3d4e5f60718293a4b5c6d7e8f90a1b2"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/api-keys/ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1/rotate \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"overlap_hours\": 48\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/api-keys/ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1/rotate\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"overlap_hours\": 48\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/api-keys/ozav_key_01J8Z9K2M3N4P5Q6R7S8T9U0V1/rotate\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"overlap_hours\": 48,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/webhook-endpoints": {
      "post": {
        "operationId": "createWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Register a webhook endpoint",
        "description": "Registers an HTTPS endpoint to receive signed event notifications for the\nsubscribed `enabled_events`. The signing `secret` (`ozav_whsec_`) is\nreturned exactly once, in this response - store it securely; it is used to\nverify delivery signatures and cannot be retrieved again.\n\nThe hostname is checked at registration. A host that does not resolve is\nrefused with `422 WEBHOOK_URL_HOST_NOT_FOUND`, because a typo there fails\nsilently forever: deliveries never reach anything and the only trace is a\nnull `response_status` in `GET /webhook-endpoints/{id}/deliveries`. Only a\nnon-existent hostname is refused - an endpoint behind a firewall, not yet\ndeployed, or slow to answer registers normally.\n",
        "x-ozav-entitlement": "developer",
        "x-ozav-scopes": [
          "webhooks:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointCreateRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Subscribe to customer + screening events",
                  "value": {
                    "url": "https://api.acme.com/ozav/webhooks",
                    "enabled_events": [
                      "customer.created",
                      "screening.completed"
                    ],
                    "description": "production"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint registered; the signing secret is in `secret` (shown once).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"url\": \"https://api.acme.com/ozav/webhooks\",\n    \"enabled_events\": [\n      \"customer.created\",\n      \"screening.completed\"\n    ],\n    \"description\": \"production\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"url\": \"https://api.acme.com/ozav/webhooks\",\n    \"enabled_events\": [\n      \"customer.created\",\n      \"screening.completed\"\n    ],\n    \"description\": \"production\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/webhook-endpoints\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"url\": \"https://api.acme.com/ozav/webhooks\",\n        \"enabled_events\": [\n            \"customer.created\",\n            \"screening.completed\",\n        ],\n        \"description\": \"production\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "get": {
        "operationId": "listWebhookEndpoints",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook endpoints",
        "description": "Lists your webhook endpoints using the standard cursor pagination. Secrets are never returned.",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "webhooks:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookEndpoint"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/webhook-endpoints\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/webhook-endpoints/{webhook_endpoint_id}": {
      "parameters": [
        {
          "name": "webhook_endpoint_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Opaque webhook endpoint id, prefixed `ozav_wh_`."
        }
      ],
      "get": {
        "operationId": "getWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Retrieve a webhook endpoint",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "webhooks:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The webhook endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "patch": {
        "operationId": "updateWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Update a webhook endpoint",
        "description": "Update any subset of url, enabled_events, status, description. The signing secret is unchanged (use rotate-secret).",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "webhooks:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointUpdateRequest"
              },
              "examples": {
                "change_events": {
                  "summary": "Subscribe to a different set of events",
                  "value": {
                    "enabled_events": [
                      "transfer.completed",
                      "transfer.failed",
                      "deposit.credited"
                    ]
                  }
                },
                "disable": {
                  "summary": "Disable an endpoint without deleting it",
                  "value": {
                    "status": "disabled"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated webhook endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id} \\\n  -X PATCH \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"enabled_events\": [\n      \"transfer.completed\",\n      \"transfer.failed\",\n      \"deposit.credited\"\n    ]\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"enabled_events\": [\n      \"transfer.completed\",\n      \"transfer.failed\",\n      \"deposit.credited\"\n    ]\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.patch(\n    \"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"enabled_events\": [\n            \"transfer.completed\",\n            \"transfer.failed\",\n            \"deposit.credited\",\n        ],\n    },\n)\ndata = response.json()"
          }
        ]
      },
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook endpoint",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "webhooks:write"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "object": {
                      "type": "string",
                      "enum": [
                        "webhook_endpoint"
                      ]
                    },
                    "deleted": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id} \\\n  -X DELETE \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}\", {\n  method: \"DELETE\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.delete(\n    \"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/webhook-endpoints/{webhook_endpoint_id}/rotate-secret": {
      "parameters": [
        {
          "name": "webhook_endpoint_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Opaque webhook endpoint id, prefixed `ozav_wh_`."
        }
      ],
      "post": {
        "operationId": "rotateWebhookEndpointSecret",
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotate a webhook endpoint's signing secret",
        "description": "Generates a new signing secret and returns it once. The previous secret\nstops signing immediately, so update your verifier before rotating.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "webhooks:write"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The endpoint with its new `secret` (shown once).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/rotate-secret \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/rotate-secret\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/rotate-secret\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/webhook-endpoints/{webhook_endpoint_id}/test": {
      "parameters": [
        {
          "name": "webhook_endpoint_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Opaque webhook endpoint id, prefixed `ozav_wh_`."
        }
      ],
      "post": {
        "operationId": "testWebhookEndpoint",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event",
        "description": "Queues a signed `webhook.test` event for this endpoint, even if\n`webhook.test` is not in `enabled_events`. The response is the queued\ndelivery row; delivery itself is asynchronous and retried by OZAV.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "webhooks:write"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The queued test delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/test \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/test\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/test\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/webhook-endpoints/{webhook_endpoint_id}/deliveries": {
      "parameters": [
        {
          "name": "webhook_endpoint_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Opaque webhook endpoint id, prefixed `ozav_wh_`."
        }
      ],
      "get": {
        "operationId": "listWebhookDeliveries",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook deliveries",
        "description": "Lists recent delivery attempts for one webhook endpoint. Response bodies\nare truncated previews; endpoint secrets, secret hashes, Vault refs, and\ninternal ids are never returned.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "webhooks:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookDelivery"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/deliveries \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/deliveries\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/webhook-endpoints/{webhook_endpoint_id}/deliveries\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/events": {
      "get": {
        "operationId": "listEvents",
        "tags": [
          "Webhooks"
        ],
        "summary": "List events",
        "description": "Lists your account's events, newest first - the pull side of the event\nlog. Each item is the same canonical envelope a webhook delivers, so you\ncan reconcile or backfill a missed delivery without a webhook endpoint.\nClient-scoped; keyset-paginated.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "events:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookEvent"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/events \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/events\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/events\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/events/{event_id}": {
      "parameters": [
        {
          "name": "event_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Opaque event id, prefixed `ozav_evt_`."
        }
      ],
      "get": {
        "operationId": "getEvent",
        "tags": [
          "Webhooks"
        ],
        "summary": "Retrieve an event",
        "description": "Fetches a single event by id - the same canonical envelope a webhook\ndelivers. A key finds only the events of its own environment, like\n`GET /events`: an event of the other environment answers `404`.\n",
        "x-ozav-entitlement": "any",
        "x-ozav-scopes": [
          "events:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The event.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEvent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/events/{event_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/events/{event_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/events/{event_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/realized-gains": {
      "get": {
        "operationId": "listRealizedGains",
        "tags": [
          "Yield"
        ],
        "summary": "Realized gain per month",
        "description": "The gain realized by redemptions, per month and per position, as input for a tax computation. It is\nnot a tax filing: there is no fiat conversion (`currency` is always `asset_minor_units`) and no filing\nformat.\n\n- Every amount is a decimal integer string in the asset's smallest unit, so it keeps its precision\n  above 2^53.\n- The month is the month of the block, not of when OZAV recorded the movement.\n- Each position is computed on its own, so a redemption in one never consumes the cost basis of\n  another.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "Realized gain per month and position.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "currency": {
                          "type": "string",
                          "enum": [
                            "asset_minor_units"
                          ],
                          "description": "Always the asset smallest unit; no fiat conversion is applied."
                        },
                        "note": {
                          "type": "string"
                        },
                        "months": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "month": {
                                "type": "string",
                                "example": "2026-02"
                              },
                              "chain": {
                                "type": "string"
                              },
                              "vault_address": {
                                "type": "string"
                              },
                              "asset": {
                                "type": "string"
                              },
                              "realized_gain": {
                                "type": "string",
                                "description": "Decimal integer string; may be negative.",
                                "example": "200000"
                              },
                              "remaining_cost_basis": {
                                "type": "string",
                                "example": "0"
                              },
                              "remaining_shares": {
                                "type": "string",
                                "example": "0"
                              },
                              "movements": {
                                "type": "integer",
                                "example": 1
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "two_months": {
                    "summary": "Deposit in January, redemption with gain in February",
                    "value": {
                      "data": {
                        "currency": "asset_minor_units",
                        "note": "realized gain in the asset minor unit; no fiat conversion is applied",
                        "months": [
                          {
                            "month": "2026-01",
                            "chain": "base",
                            "asset": "USDC",
                            "vault_address": "0x1234",
                            "realized_gain": "0",
                            "remaining_cost_basis": "1000000",
                            "remaining_shares": "1000000",
                            "movements": 1
                          },
                          {
                            "month": "2026-02",
                            "chain": "base",
                            "asset": "USDC",
                            "vault_address": "0x1234",
                            "realized_gain": "200000",
                            "remaining_cost_basis": "0",
                            "remaining_shares": "0",
                            "movements": 1
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "description": "The movement reader is not wired in this deployment.",
            "content": {
              "application/json": {
                "examples": {
                  "unavailable": {
                    "summary": "Refusal, never an empty list",
                    "value": {
                      "error": {
                        "type": "api_error",
                        "code": "YIELD_MOVEMENTS_UNAVAILABLE",
                        "message": "realized gains are unavailable in this deployment"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/realized-gains \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/realized-gains\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/yield/realized-gains\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/strategies": {
      "get": {
        "operationId": "listYieldStrategies",
        "tags": [
          "Yield"
        ],
        "summary": "List yield strategies",
        "description": "The yield strategies open to your organization: asset, network and rate. The catalogue is the same\nfor every organization with access.\n\n- `apy_bps` is the indicative rate, refreshed off chain. It is `null` when there is no recent figure,\n  which is not the same as zero.\n- `realized_apy_7d` and `realized_apy_30d` are measured from the strategy's share price, recorded\n  hourly. When a realized figure cannot be stated, `known` is `false` and `reason` says why:\n  `no_data`, `window_not_covered` (the series started inside the window), `window_too_short` (under\n  12 hours between readings), `price_went_backwards` (the share price fell) or `unusable_price`. When\n  `known` is `true`, `observed_hours` is the period actually measured.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The strategies open to the client.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "chain": {
                            "type": "string"
                          },
                          "asset": {
                            "type": "string"
                          },
                          "apy_bps": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "realized_apy_7d": {
                            "$ref": "#/components/schemas/RealizedApy"
                          },
                          "realized_apy_30d": {
                            "$ref": "#/components/schemas/RealizedApy"
                          },
                          "risk": {
                            "$ref": "#/components/schemas/YieldRisk"
                          }
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "one": {
                    "summary": "One strategy, 7d measured and 30d not yet covered",
                    "value": {
                      "data": [
                        {
                          "id": "base-usdc-prime",
                          "chain": "base",
                          "asset": "USDC",
                          "apy_bps": 445,
                          "realized_apy_7d": {
                            "known": true,
                            "bps": 512,
                            "observed_hours": 167,
                            "points": 168
                          },
                          "realized_apy_30d": {
                            "known": false,
                            "reason": "window_not_covered"
                          },
                          "risk": {
                            "management": "curated",
                            "curator_ref": "curator-1",
                            "issuer_freeze": "none",
                            "redemption_form": "immediate",
                            "principal_guaranteed": false,
                            "smart_contract_risk": true,
                            "measured_at": "2026-08-06"
                          }
                        }
                      ]
                    }
                  },
                  "queued": {
                    "summary": "A strategy whose redemption is a request, and whose issuer holds a key",
                    "value": {
                      "data": [
                        {
                          "id": "solana-usd-treasury",
                          "chain": "solana",
                          "asset": "USDC",
                          "apy_bps": null,
                          "realized_apy_7d": {
                            "known": false,
                            "reason": "no_data"
                          },
                          "realized_apy_30d": {
                            "known": false,
                            "reason": "no_data"
                          },
                          "risk": {
                            "management": "direct",
                            "curator_ref": null,
                            "issuer_freeze": "unilateral_key",
                            "redemption_form": "by_request",
                            "principal_guaranteed": false,
                            "smart_contract_risk": true,
                            "measured_at": "2026-08-09"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/strategies \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/strategies\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/yield/strategies\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/wallets/{address}/positions": {
      "get": {
        "operationId": "listYieldPositionsByAddress",
        "tags": [
          "Yield"
        ],
        "summary": "List yield positions for a wallet you control",
        "description": "The yield positions held by a wallet address you supply, for a holder who keeps their own key. The\naddress comes from the path, because a balance on chain is public; the organization making the call\nalways comes from your API key.\n\nThe address is read only on the networks of its own family (an EVM address is not queried on Solana),\nso `unreadable_chains` and `complete` only reflect networks that apply.\n\n`cost_basis` is `null` here: the deposit did not pass through OZAV, so there is no record of what it\ncost. These positions report `accounting: \"untracked\"` with the reason. What a position is worth today\nis read from the chain.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "yield:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The wallet address. EVM (`0x` + 40 hex) or Solana (base58). An\naddress matching neither is refused with `WALLET_ADDRESS_INVALID`\nrather than searched for, because \"not found\" and \"mistyped\" read\nthe same and mean opposite things.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The positions held by that address.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "positions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "chain": {
                            "type": "string"
                          },
                          "asset": {
                            "type": "string"
                          },
                          "balance": {
                            "type": "string"
                          },
                          "apy_bps": {
                            "type": "integer"
                          },
                          "cost_basis": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Always `null` on this route. The deposit did not pass\nthrough OZAV, so there is no book. `null` is the\nabsence of the number, never zero, which would be a\nclaim that nothing was paid.\n"
                          },
                          "unrealized": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "realized": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "accounting": {
                            "type": "string",
                            "description": "`untracked` on this route, with the reason attached.\n"
                          },
                          "redemption": {
                            "$ref": "#/components/schemas/YieldRedemption"
                          }
                        }
                      }
                    },
                    "unreadable_chains": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "complete": {
                      "type": "boolean"
                    }
                  }
                },
                "examples": {
                  "one": {
                    "summary": "A wallet OZAV has never seen, so no acquisition cost",
                    "value": {
                      "positions": [
                        {
                          "chain": "base",
                          "asset": "USDC",
                          "balance": "42180550",
                          "apy_bps": 445,
                          "cost_basis": null,
                          "unrealized": null,
                          "realized": null,
                          "accounting": "untracked",
                          "redemption": {
                            "kind": "available",
                            "available_to_withdraw": "42180550"
                          }
                        }
                      ],
                      "unreadable_chains": [],
                      "complete": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/wallets/{address}/positions \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/wallets/{address}/positions\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/yield/wallets/{address}/positions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/applications": {
      "post": {
        "operationId": "createYieldApplication",
        "tags": [
          "Yield"
        ],
        "summary": "Apply custodied balance to a yield strategy",
        "description": "Instructs OZAV to apply your organization's custodied balance to a strategy. OZAV signs with its own\nkey, under a two-party quorum. This is the custody counterpart of `POST /yield/deposits/build`, which\nreturns unsigned transactions for a wallet you hold; the two take different scopes (`yield:apply`\nhere, `yield:write` there).\n\n**Closed pilot.** OZAV grants `yield:apply` per organization; asking for it when minting a key answers\n`403 SCOPE_NOT_HELD` unless the key you mint with already holds it. With the scope, an application is still refused with\n`MANAGED_YIELD_NOT_ENABLED` while the product is off for the environment, and with\n`MANAGED_YIELD_CLIENT_NOT_ENABLED` while it is off for your organization. Both arrive as the\napplication's `refused` state, with the cause in `detail`.\n\n**Answers `202`.** Signing waits on a second approval, so the response carries an `id`, and the state\nis read from `GET /yield/applications/{application_id}`.\n\n**One application in flight per strategy.** A second call while one is `pending` or `running` answers\n`202` with the existing application and `duplicate: true`. The lock is shared with redemptions: while a\nredemption from the strategy is in flight, an application answers `409 YIELD_REDEMPTION_IN_FLIGHT`.\n\nThis route checks that the strategy exists and that the amount is positive. Authorisation and\nexposure limits are checked when the application runs; a refusal there shows up as\n`status: refused`, with the cause in `detail`.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:apply"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "strategy_id",
                  "amount"
                ],
                "properties": {
                  "strategy_id": {
                    "type": "string",
                    "description": "The strategy to apply to, as listed by `GET /yield/strategies`."
                  },
                  "amount": {
                    "type": "string",
                    "description": "Amount in the asset's smallest unit, as a decimal integer\nstring. A JSON number would lose precision above 2^53.\n"
                  }
                }
              },
              "examples": {
                "apply": {
                  "summary": "Apply 100 USDC (smallest unit) to a strategy",
                  "value": {
                    "strategy_id": "base-usdc-prime",
                    "amount": "100000000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. The application is recorded; read its state by id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/YieldApplication"
                    },
                    "duplicate": {
                      "type": "boolean",
                      "description": "True when an application was already in flight for this\nstrategy and `data` is that one, not a new one. `data.amount`\nis that order's amount and may differ from the one just sent:\nthe request was accepted as a no-op, not as a new instruction.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/applications \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/applications\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/yield/applications\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"strategy_id\": \"base-usdc-prime\",\n        \"amount\": \"100000000\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/applications/{application_id}": {
      "get": {
        "operationId": "getYieldApplication",
        "tags": [
          "Yield"
        ],
        "summary": "Read the state of a yield application",
        "description": "The state of an application created by `POST /yield/applications`:\n\n- `pending`: recorded, waiting to run;\n- `running`: being executed;\n- `applied`: confirmed on chain;\n- `refused`: declined by a check, and no money moved;\n- `failed`: attempted and not completed.\n\n`detail` carries the cause for `refused` and `failed`, and `tx_hash` is present only when a\ntransaction existed. An application of another organization answers `404`.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "application_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The application.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/YieldApplication"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/applications/{application_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/applications/{application_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/yield/applications/{application_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/redemptions": {
      "post": {
        "operationId": "createYieldRedemption",
        "tags": [
          "Yield"
        ],
        "summary": "Redeem custodied balance from a yield strategy",
        "description": "Instructs OZAV to take your organization's custodied balance back out of a strategy, onto your\ncustody address. OZAV signs with its own key, under the same two-party quorum as\n`POST /yield/applications`, and the same scope (`yield:apply`) authorizes both directions.\n\n**Behind a switch that starts off.** While redemptions are not enabled for the environment, this\nroute answers `503 MANAGED_YIELD_REDEEM_NOT_ENABLED` and does nothing.\n\n**A redemption is always allowed.** Turning managed yield off, for the environment or for your\norganization, stops new applications only. It never blocks taking the balance out, and it never\nrefuses a redemption already queued.\n\n**Send `amount` or `all: true`, never both.** `amount` is in the asset's smallest unit, as a decimal\ninteger string, and redeems that value. `all: true` redeems every share you hold in the strategy\nwhen the order is accepted, so value earned up to the moment it runs comes out with it.\n\n**Checked against your position before it is recorded.** An `amount` above the position answers\n`409 YIELD_REDEMPTION_EXCEEDS_POSITION`. Above what the strategy releases right now (see\n`redemption` on `GET /yield/positions`) it answers `409 WITHDRAW_EXCEEDS_AVAILABLE`; `all: true`\nneeds the whole position to be releasable. To leave a strategy completely, send `all: true`: an\n`amount` equal to the displayed balance can be refused by the network, which rounds the shares a\nwithdrawal burns up and the displayed balance down.\n\n**`idempotency_key` is required.** The same key with the same body returns the same redemption\n(`202`), whatever its state; with a different body it answers `409 IDEMPOTENCY_KEY_REUSED`. Use a new\nkey for a new redemption.\n\n**One order in flight per strategy, in either direction.** A second redemption while one is\n`pending`, `running` or `indeterminate` answers `202` with that one and `duplicate: true`. While an\napplication to the strategy is in flight, a redemption answers `409 YIELD_APPLICATION_IN_FLIGHT`.\n\n**Answers `202`.** Signing waits on a second approval; read the state from\n`GET /yield/redemptions/{redemption_id}`. The realized gain of the redemption appears in\n`GET /yield/realized-gains` once the transaction is recorded.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:apply"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "idempotency_key",
                  "strategy_id"
                ],
                "properties": {
                  "idempotency_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "pattern": "^[A-Za-z0-9._:-]+$",
                    "description": "A new key for each new redemption; the same key to repeat one that did not answer."
                  },
                  "strategy_id": {
                    "type": "string",
                    "description": "The strategy to redeem from, as listed by `GET /yield/strategies`."
                  },
                  "amount": {
                    "type": "string",
                    "description": "Amount in the asset's smallest unit, as a decimal integer\nstring. A JSON number would lose precision above 2^53. Omit when `all` is true.\n"
                  },
                  "all": {
                    "type": "boolean",
                    "description": "Redeem every share held in the strategy. Omit `amount` when true."
                  }
                }
              },
              "examples": {
                "redeem": {
                  "summary": "Redeem 500 USDC (smallest unit) from a strategy",
                  "value": {
                    "idempotency_key": "redeem-2026-10-16-001",
                    "strategy_id": "base-usdc-prime",
                    "amount": "500000000"
                  }
                },
                "redeemAll": {
                  "summary": "Redeem the whole position",
                  "value": {
                    "idempotency_key": "redeem-2026-10-16-002",
                    "strategy_id": "base-usdc-prime",
                    "all": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. The redemption is recorded; read its state by id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/YieldRedemptionOrder"
                    },
                    "duplicate": {
                      "type": "boolean",
                      "description": "True when a redemption was already in flight for this\nstrategy and `data` is that one, not a new one. The request\nwas accepted as a no-op, not as a new instruction.\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "description": "`MANAGED_YIELD_REDEEM_NOT_ENABLED` - redemptions are not enabled here; nothing was done.\n`YIELD_POSITION_UNREADABLE` or `WITHDRAW_LIMIT_UNREADABLE` - transient; retry with the same\n`idempotency_key`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/redemptions \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"redeem-2026-10-16-001\",\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"500000000\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/redemptions\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"idempotency_key\": \"redeem-2026-10-16-001\",\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"500000000\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/yield/redemptions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"idempotency_key\": \"redeem-2026-10-16-001\",\n        \"strategy_id\": \"base-usdc-prime\",\n        \"amount\": \"500000000\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/redemptions/{redemption_id}": {
      "get": {
        "operationId": "getYieldRedemption",
        "tags": [
          "Yield"
        ],
        "summary": "Read the state of a yield redemption",
        "description": "The state of a redemption created by `POST /yield/redemptions`:\n\n- `pending`: recorded, waiting to run;\n- `running`: being executed;\n- `redeemed`: confirmed on chain, and the balance is back on your custody address;\n- `refused`: declined by a check, and no money moved;\n- `failed`: attempted and nothing moved;\n- `indeterminate`: stopped without knowing whether the money moved. Do not order it again.\n\n`detail` carries the cause, and `tx_hash` is present whenever a transaction is known. A redemption\nof another organization answers `404`. Readable while redemptions are switched off.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:read"
        ],
        "x-ozav-availability": "live",
        "parameters": [
          {
            "name": "redemption_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The redemption.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/YieldRedemptionOrder"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/redemptions/{redemption_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/redemptions/{redemption_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/yield/redemptions/{redemption_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/positions": {
      "get": {
        "operationId": "listYieldPositions",
        "tags": [
          "Yield"
        ],
        "summary": "List yield positions",
        "description": "Your organization's yield positions, in the wallet OZAV holds for you or in the self-custody wallet\nyou registered, with the amount applied, the acquisition cost and the accrued gain. The owner comes\nfrom your API key.\n\n`unreadable_chains` lists networks that could not be read. An empty list with entries in\n`unreadable_chains` is not \"holding nothing\": check `complete` before treating the result as the full\npicture.\n\n`redemption` says what can leave a position right now, which can differ from `balance` (what it is\nworth) when the market is fully used:\n\n- `available`: `available_to_withdraw` can be redeemed;\n- `held`: the balance is there and still earning, and nothing can be redeemed right now (queue, pause\n  or illiquidity);\n- `unreadable`: the limit could not be read, and `available_to_withdraw` is `null`, never `\"0\"`.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:read"
        ],
        "x-ozav-availability": "live",
        "responses": {
          "200": {
            "description": "The client's yield positions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "positions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "chain": {
                            "type": "string"
                          },
                          "asset": {
                            "type": "string"
                          },
                          "balance": {
                            "type": "string"
                          },
                          "apy_bps": {
                            "type": "integer"
                          },
                          "cost_basis": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "unrealized": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "realized": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "accounting": {
                            "type": "string"
                          },
                          "redemption": {
                            "$ref": "#/components/schemas/YieldRedemption"
                          }
                        }
                      }
                    },
                    "unreadable_chains": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "complete": {
                      "type": "boolean"
                    }
                  }
                },
                "examples": {
                  "one": {
                    "summary": "One position, every chain read",
                    "value": {
                      "positions": [
                        {
                          "chain": "base",
                          "asset": "USDC",
                          "balance": "42180550",
                          "apy_bps": 445,
                          "cost_basis": "41000000",
                          "unrealized": "1180550",
                          "realized": "0",
                          "accounting": "complete",
                          "redemption": {
                            "kind": "available",
                            "available_to_withdraw": "42180550"
                          }
                        }
                      ],
                      "unreadable_chains": [],
                      "complete": true
                    }
                  },
                  "held": {
                    "summary": "The balance is there and cannot be redeemed right now",
                    "value": {
                      "positions": [
                        {
                          "chain": "base",
                          "asset": "USDC",
                          "balance": "42180550",
                          "apy_bps": 445,
                          "cost_basis": "41000000",
                          "unrealized": "1180550",
                          "realized": "0",
                          "accounting": "complete",
                          "redemption": {
                            "kind": "held",
                            "available_to_withdraw": "0"
                          }
                        }
                      ],
                      "unreadable_chains": [],
                      "complete": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/positions \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/positions\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/yield/positions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/preview": {
      "post": {
        "operationId": "previewYieldDeposit",
        "tags": [
          "Yield"
        ],
        "summary": "Preview applying to a yield strategy",
        "description": "Prices an application before it is made: the strategy's cap and the network cost of each on-chain\nstep. Nothing is written and nothing leaves any wallet; it is a `POST` because it carries a body. The\nowner comes from your API key.\n\nThere is no fixed minimum. Send an amount and read the refusal:\n\n- `AMOUNT_IN_TOO_HIGH`: above the cap the strategy accepts right now, read from the contract.\n- `AMOUNT_IN_TOO_LOW`: the network cost is not less than the amount, so applying would lose value.\n  This floor rises when the network gets more expensive.\n\n`network_cost` sums every step: applying is usually two transactions, an allowance and then the\ndeposit. If any step cannot be measured, the total is withheld and `network_cost_unknown_reason`\nnames the cause. `network_cost` is what the network charges, not a charge by OZAV.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "yield:read"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "strategy_id",
                  "amount"
                ],
                "properties": {
                  "strategy_id": {
                    "type": "string",
                    "description": "The `id` from `GET /yield/strategies`.",
                    "example": "base-usdc-prime"
                  },
                  "amount": {
                    "type": "string",
                    "description": "Decimal integer string, in the asset's smallest unit. Never a\nfloat: floats lose precision in money silently.\n",
                    "pattern": "^[0-9]{1,32}$",
                    "example": "100000000"
                  }
                }
              },
              "examples": {
                "preview": {
                  "summary": "Preview the yield on 100 USDC before committing",
                  "value": {
                    "strategy_id": "base-usdc-prime",
                    "amount": "100000000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The application priced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "amount": {
                      "type": "string"
                    },
                    "network_cost": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/NetworkCost"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "network_cost_unknown_reason": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "gas_unknown",
                        "price_unknown",
                        "unusable_value",
                        null
                      ],
                      "description": "Why the cost is not stated, when it is not. `null` when it\nis. A null cost with no reason would not distinguish \"the\nnetwork charges nothing\" from \"it could not be measured\".\n"
                    }
                  }
                },
                "examples": {
                  "measured": {
                    "summary": "Both steps measured",
                    "value": {
                      "ok": true,
                      "amount": "100000000",
                      "network_cost": {
                        "amount": "0.60",
                        "currency": "USD"
                      },
                      "network_cost_unknown_reason": null
                    }
                  },
                  "unmeasured": {
                    "summary": "First application, so the deposit step cannot be estimated yet",
                    "value": {
                      "ok": true,
                      "amount": "100000000",
                      "network_cost": null,
                      "network_cost_unknown_reason": "gas_unknown"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/preview \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/preview\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/yield/preview\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"strategy_id\": \"base-usdc-prime\",\n        \"amount\": \"100000000\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/deposits/build": {
      "post": {
        "operationId": "buildYieldDeposit",
        "tags": [
          "Yield"
        ],
        "summary": "Build an unsigned yield application",
        "description": "Returns the ordered, unsigned transactions that apply funds to a strategy, for your organization's own\nwallet to sign and broadcast in the order returned (the deposit needs the allowance from the step before it).\nOZAV neither signs nor broadcasts: `signed_by` says so, and `signer` names the wallet the transactions\nwere built for, to check before signing.\n\n- **Self-custody only.** A client under managed custody holds no key, and the request is refused with\n  `CUSTODY_MODE_NOT_SELF_CUSTODY`.\n- **The registered wallet only.** If `address` is sent, it must be the wallet already registered for\n  your organization on that network; any other address answers `403 WALLET_ADDRESS_NOT_REGISTERED`,\n  even when it is valid. Omit `address` to use the registered wallet. Register it first with\n  `POST /wallets`. There is one registered address per organization, on any network: registering\n  again replaces it, and a build on the old network then answers `409 NO_WALLET_ON_CHAIN`. OZAV tracks\n  positions by the addresses it knows, so an unregistered address would hold a position outside its\n  records. `GET /yield/wallets/{address}/positions` still reads any address.\n- The strategy's contract address appears in `to`, because your user signs a transaction to it. The\n  protocol's name is not returned.\n\n**Closed pilot, two checks in series.** While building is off for the environment the route answers\n`503 YIELD_BUILD_NOT_ENABLED`. Once it is on, an organization not enrolled answers\n`403 YIELD_BUILD_NOT_IN_PILOT`. If enrolment cannot be read, it answers\n`503 YIELD_PILOT_STATE_UNREADABLE`: retry, rather than ask for access.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "yield:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "strategy_id",
                  "amount"
                ],
                "properties": {
                  "strategy_id": {
                    "type": "string",
                    "example": "base-usdc-prime"
                  },
                  "amount": {
                    "type": "string",
                    "pattern": "^[0-9]{1,32}$",
                    "example": "100000000"
                  }
                }
              },
              "examples": {
                "build_deposit": {
                  "summary": "Build the unsigned deposit for 100 USDC",
                  "value": {
                    "strategy_id": "base-usdc-prime",
                    "amount": "100000000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The unsigned transactions, in signing order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transactions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kind": {
                            "type": "string",
                            "enum": [
                              "approve",
                              "deposit"
                            ]
                          },
                          "chain": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The contract to call. The token for `approve`, the vault for `deposit`."
                          },
                          "data": {
                            "type": "string",
                            "description": "Unsigned calldata."
                          },
                          "value": {
                            "type": "string",
                            "description": "Native value. Always \"0\" in ERC-20 flows."
                          }
                        }
                      }
                    },
                    "signer": {
                      "type": "string",
                      "description": "The wallet these transactions were built for, derived from\nthe authenticated client. Check it against the wallet about\nto sign.\n"
                    },
                    "signed_by": {
                      "type": "string",
                      "enum": [
                        "client"
                      ],
                      "description": "OZAV does not sign and does not broadcast here."
                    }
                  }
                },
                "examples": {
                  "two_steps": {
                    "summary": "Allowance then deposit",
                    "value": {
                      "transactions": [
                        {
                          "kind": "approve",
                          "chain": "base",
                          "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                          "data": "0x095ea7b3",
                          "value": "0"
                        },
                        {
                          "kind": "deposit",
                          "chain": "base",
                          "to": "0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61",
                          "data": "0x6e553f65",
                          "value": "0"
                        }
                      ],
                      "signer": "0x1111111111111111111111111111111111111111",
                      "signed_by": "client"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/deposits/build \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/deposits/build\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/yield/deposits/build\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"strategy_id\": \"base-usdc-prime\",\n        \"amount\": \"100000000\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/yield/withdrawals/build": {
      "post": {
        "operationId": "buildYieldWithdrawal",
        "tags": [
          "Yield"
        ],
        "summary": "Build an unsigned yield withdrawal",
        "description": "Returns the unsigned transaction that withdraws from a strategy, for your organization's own wallet to\nsign and broadcast. One transaction: burning your own shares needs no allowance. OZAV neither signs nor\nbroadcasts: `signed_by` says so, and `signer` names the wallet the transaction was built for.\n\n- **Denominated in the asset, not in shares**, so no conversion rounds against the holder.\n- **Built only if the strategy will release it.** OZAV reads how much can leave right now before\n  building. `WITHDRAW_EXCEEDS_AVAILABLE`: the balance is there and still earning, and less can leave\n  right now. `WITHDRAW_LIMIT_UNREADABLE`: the limit could not be read.\n- **Self-custody only.** A client under managed custody is refused with\n  `CUSTODY_MODE_NOT_SELF_CUSTODY`.\n- **The registered wallet only.** If `address` is sent, it must be the wallet already registered for\n  your organization on that network; any other answers `403 WALLET_ADDRESS_NOT_REGISTERED`, because\n  the shares are held by the registered wallet. There is one registered address per organization, on\n  any network: registering again with `POST /wallets` replaces it.\n- The strategy's contract address appears in `to`; the protocol's name is not returned.\n\n**Closed pilot, two checks in series:** `503 YIELD_BUILD_NOT_ENABLED` while building is off for the\nenvironment, `403 YIELD_BUILD_NOT_IN_PILOT` for an organization not enrolled, and\n`503 YIELD_PILOT_STATE_UNREADABLE` when enrolment cannot be read.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "yield:write"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "strategy_id",
                  "amount"
                ],
                "properties": {
                  "strategy_id": {
                    "type": "string",
                    "example": "base-usdc-prime"
                  },
                  "amount": {
                    "type": "string",
                    "pattern": "^[0-9]{1,32}$",
                    "example": "100000000"
                  }
                }
              },
              "examples": {
                "build_withdrawal": {
                  "summary": "Build the unsigned withdrawal for 100 USDC",
                  "value": {
                    "strategy_id": "base-usdc-prime",
                    "amount": "100000000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The unsigned withdrawal transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transactions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kind": {
                            "type": "string",
                            "enum": [
                              "withdraw"
                            ]
                          },
                          "chain": {
                            "type": "string"
                          },
                          "to": {
                            "type": "string",
                            "description": "The contract to call. The token for `approve`, the vault for `deposit`."
                          },
                          "data": {
                            "type": "string",
                            "description": "Unsigned calldata."
                          },
                          "value": {
                            "type": "string",
                            "description": "Native value. Always \"0\" in ERC-20 flows."
                          }
                        }
                      }
                    },
                    "signer": {
                      "type": "string",
                      "description": "The wallet these transactions were built for, derived from\nthe authenticated client. Check it against the wallet about\nto sign.\n"
                    },
                    "signed_by": {
                      "type": "string",
                      "enum": [
                        "client"
                      ],
                      "description": "OZAV does not sign and does not broadcast here."
                    }
                  }
                },
                "examples": {
                  "one_step": {
                    "summary": "A single withdrawal call",
                    "value": {
                      "transactions": [
                        {
                          "kind": "withdraw",
                          "chain": "base",
                          "to": "0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61",
                          "data": "0xb460af94",
                          "value": "0"
                        }
                      ],
                      "signer": "0x1111111111111111111111111111111111111111",
                      "signed_by": "client"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/yield/withdrawals/build \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/yield/withdrawals/build\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"strategy_id\": \"base-usdc-prime\",\n    \"amount\": \"100000000\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/yield/withdrawals/build\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"strategy_id\": \"base-usdc-prime\",\n        \"amount\": \"100000000\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/network-transfers": {
      "post": {
        "operationId": "createNetworkTransfer",
        "tags": [
          "Network Transfers"
        ],
        "summary": "Move your own USDC to another network",
        "description": "**Not callable** (`coming_soon`). No API key can be issued with the `network_transfers:write` scope\nthis route requires, so it answers `403 INSUFFICIENT_SCOPE` for every key. The contract below shows\nthe request and the response.\n\nMoves USDC you already hold from one network to another, in the same wallet. It is not a payment:\norigin and destination are the same holder.\n\n- **Answers `202`.** The transfer burns on the origin network under a two-party signing quorum, waits\n  for attestation and then mints on the destination, which takes minutes. The response carries an\n  `id`; read the state from `GET /network-transfers/{transfer_id}`.\n- **One transfer in flight per source network.** A second request while one is in flight answers\n  `202` with the transfer already running and `duplicate: true`.\n- **The holder comes from your key**, never from the body.\n- **The amount** is a decimal integer string in the asset's smallest unit.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "network_transfers:write"
        ],
        "x-ozav-availability": "coming_soon",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "source_chain",
                  "destination_chain",
                  "asset",
                  "amount"
                ],
                "additionalProperties": false,
                "properties": {
                  "source_chain": {
                    "type": "string",
                    "example": "ethereum"
                  },
                  "destination_chain": {
                    "type": "string",
                    "example": "base"
                  },
                  "asset": {
                    "type": "string",
                    "description": "Only USDC crosses today.",
                    "example": "USDC"
                  },
                  "amount": {
                    "type": "string",
                    "description": "Decimal integer string, smallest unit of `asset`. Never a float.",
                    "pattern": "^[1-9][0-9]{0,77}$",
                    "example": "5000000"
                  }
                }
              },
              "examples": {
                "ethereum_to_base": {
                  "summary": "Move 5 USDC from Ethereum to Base",
                  "value": {
                    "source_chain": "ethereum",
                    "destination_chain": "base",
                    "asset": "USDC",
                    "amount": "5000000"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. Read the state with `GET /network-transfers/{transfer_id}`.\n`duplicate` is true when a transfer was already in flight for that\nsource network; the transfer returned is that one.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NetworkTransfer"
                    },
                    "duplicate": {
                      "type": "boolean"
                    }
                  }
                },
                "examples": {
                  "accepted": {
                    "summary": "Accepted",
                    "value": {
                      "data": {
                        "id": "3f2a1b4c-5d6e-4f70-8a91-b2c3d4e5f607",
                        "status": "pending",
                        "source_chain": "ethereum",
                        "destination_chain": "base",
                        "asset": "USDC",
                        "amount": "5000000",
                        "spread_bps": null,
                        "detail": null,
                        "created_at": "2026-08-24T22:00:00.000Z"
                      },
                      "duplicate": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The key does not hold `network_transfers:write`. This is the primary\ngate and it is not self-service: the scope is outside the bootstrap,\nso `POST /api-keys` cannot grant it. OZAV grants it per account.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "`NETWORK_TRANSFER_PRICE_NOT_CONFIGURED` : no active pricing row applies\nto this account, so the transfer is refused rather than priced at zero.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "`NETWORK_TRANSFER_PRICE_UNREADABLE`: pricing could not be read, so the\ntransfer cannot be accepted. Retry shortly.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/network-transfers \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"source_chain\": \"ethereum\",\n    \"destination_chain\": \"base\",\n    \"asset\": \"USDC\",\n    \"amount\": \"5000000\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/network-transfers\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"source_chain\": \"ethereum\",\n    \"destination_chain\": \"base\",\n    \"asset\": \"USDC\",\n    \"amount\": \"5000000\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/network-transfers\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"source_chain\": \"ethereum\",\n        \"destination_chain\": \"base\",\n        \"asset\": \"USDC\",\n        \"amount\": \"5000000\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/network-transfers/{transfer_id}": {
      "get": {
        "operationId": "getNetworkTransfer",
        "tags": [
          "Network Transfers"
        ],
        "summary": "Read the state of a network transfer",
        "description": "**Reads a transfer; creating one is not callable** (`coming_soon`). This route returns the state of a\nnetwork transfer that exists; `POST /network-transfers` answers `403 INSUFFICIENT_SCOPE` for every key.\n\nA transfer of another holder answers `404`. `in_progress` covers everything between leaving the origin\nnetwork and arriving at the destination. While it is in progress, do not retry: a second request would\nmove the amount again.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "network_transfers:read"
        ],
        "x-ozav-availability": "coming_soon",
        "parameters": [
          {
            "name": "transfer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The transfer state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NetworkTransfer"
                    }
                  }
                },
                "examples": {
                  "in_progress": {
                    "summary": "Left the origin network, not yet arrived",
                    "value": {
                      "data": {
                        "id": "3f2a1b4c-5d6e-4f70-8a91-b2c3d4e5f607",
                        "status": "in_progress",
                        "source_chain": "ethereum",
                        "destination_chain": "base",
                        "asset": "USDC",
                        "amount": "5000000",
                        "spread_bps": 15,
                        "detail": null,
                        "created_at": "2026-08-24T22:00:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/network-transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1 \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/network-transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/network-transfers/ozav_tr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/swap/quote": {
      "post": {
        "operationId": "quoteSwap",
        "tags": [
          "Swap"
        ],
        "summary": "Price a same-network asset swap",
        "description": "Prices a swap between two assets on the same network, comparing the\nsources available and returning the best net rate.\n\nRead-only. It is a POST because it carries a body (the pair, the amount,\nthe tolerance), not because it writes. Nothing leaves any wallet.\n\nThe quote does not execute, and `executable` says so rather than leaving\nit to be assumed. There is no route that accepts a quote: moving value\nneeds an execution gate that no source currently passes. The absence of\nthat route is the gate, not an omission.\n\nThe wallet that receives the outgoing asset is derived from the\nauthenticated client. The body carries the pair and the amount, never a\ndestination: accepting one would let a caller price, and later execute, a\nswap whose proceeds land somewhere else.\n\n`slippage_bps` is required and has no silent default. A hidden default\nwould decide, on the client's behalf, how much they accept losing.\n\n`min_amount_out` travels beside `amount_out`, never instead of it. Showing\nonly the expected output would make the tolerance disappear from the\nanswer, and the client would compare an optimistic figure against an\nalready-discounted one elsewhere.\n\nNot every network carries a pair. A swap needs both assets settled on\nthe same network, and some networks carry only one of them. A pair no\nsource serves answers a named error, never a zero or an empty quote, so a\npair that cannot be served stays distinguishable from a bad price.\n\n`quotes_compared` and `sources_unavailable` are counts, never lists. That a\ncomparison happened is worth knowing (the best of three is a different\nanswer from the only one that replied), and the number says it without\nnaming who.\n",
        "x-ozav-entitlement": "custody",
        "x-ozav-scopes": [
          "swap:read"
        ],
        "x-ozav-availability": "live",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain",
                  "asset_in",
                  "asset_out",
                  "amount_in",
                  "slippage_bps"
                ],
                "properties": {
                  "chain": {
                    "type": "string",
                    "example": "ethereum"
                  },
                  "asset_in": {
                    "type": "string",
                    "example": "USDT"
                  },
                  "asset_out": {
                    "type": "string",
                    "example": "USDC"
                  },
                  "amount_in": {
                    "type": "string",
                    "description": "Decimal integer string, smallest unit of `asset_in`. Never a float.",
                    "pattern": "^[0-9]{1,32}$",
                    "example": "100000000"
                  },
                  "slippage_bps": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Tolerance in basis points. 50 is 0.50%. Required.",
                    "example": 50
                  }
                }
              },
              "examples": {
                "usdt_to_usdc": {
                  "summary": "Quote 100 USDT into USDC with 0.50% slippage tolerance",
                  "value": {
                    "chain": "ethereum",
                    "asset_in": "USDT",
                    "asset_out": "USDC",
                    "amount_in": "100000000",
                    "slippage_bps": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The swap priced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "quote": {
                      "type": "object",
                      "properties": {
                        "chain": {
                          "type": "string"
                        },
                        "asset_in": {
                          "type": "string"
                        },
                        "asset_out": {
                          "type": "string"
                        },
                        "amount_in": {
                          "type": "string"
                        },
                        "amount_out": {
                          "type": "string"
                        },
                        "min_amount_out": {
                          "type": "string",
                          "description": "The floor an execution would have to honour, after tolerance."
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "executable": {
                          "type": "boolean",
                          "enum": [
                            false
                          ],
                          "description": "Always false. This surface prices; it does not execute."
                        }
                      }
                    },
                    "quotes_compared": {
                      "type": "integer"
                    },
                    "sources_unavailable": {
                      "type": "integer",
                      "description": "How many sources did not answer. Never silenced: a quote\nchosen from one of three is a different answer from one\nchosen from three of three.\n"
                    }
                  }
                },
                "examples": {
                  "best_of_two": {
                    "summary": "Best of two sources",
                    "value": {
                      "quote": {
                        "chain": "ethereum",
                        "asset_in": "USDT",
                        "asset_out": "USDC",
                        "amount_in": "100000000",
                        "amount_out": "99910000",
                        "min_amount_out": "99410450",
                        "expires_at": "2026-08-08T23:00:00.000Z",
                        "executable": false
                      },
                      "quotes_compared": 1,
                      "sources_unavailable": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/swap/quote \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"chain\": \"ethereum\",\n    \"asset_in\": \"USDT\",\n    \"asset_out\": \"USDC\",\n    \"amount_in\": \"100000000\",\n    \"slippage_bps\": 50\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/swap/quote\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"chain\": \"ethereum\",\n    \"asset_in\": \"USDT\",\n    \"asset_out\": \"USDC\",\n    \"amount_in\": \"100000000\",\n    \"slippage_bps\": 50\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/swap/quote\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"chain\": \"ethereum\",\n        \"asset_in\": \"USDT\",\n        \"asset_out\": \"USDC\",\n        \"amount_in\": \"100000000\",\n        \"slippage_bps\": 50,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vault-roots": {
      "post": {
        "operationId": "createVaultRoot",
        "tags": [
          "Vaults"
        ],
        "summary": "Register the administrator keys of your vaults",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nRegisters the Ed25519 public keys that administer your vaults and the number of them that must sign\na grant or a vault. The same keys and quorum, in any order, return the existing root with `200`.\nFields the API does not know are ignored and not stored.\nWeak Ed25519 keys (small order or a torsion component) are refused.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VaultRootRequest"
              },
              "examples": {
                "default": {
                  "summary": "Request body",
                  "value": {
                    "administrators": [
                      "2012cb90ca60e8e5d8daf66e2272d2233e0486d557e8c66141ed8920177d7eb7",
                      "c050c5637a44fa8629fff3cccce2300cb362a63d99d95fc54145266f4332445a"
                    ],
                    "threshold": 2
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same root already existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultRoot"
                }
              }
            }
          },
          "201": {
            "description": "The root was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultRoot"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vault-roots \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"administrators\": [\n      \"2012cb90ca60e8e5d8daf66e2272d2233e0486d557e8c66141ed8920177d7eb7\",\n      \"c050c5637a44fa8629fff3cccce2300cb362a63d99d95fc54145266f4332445a\"\n    ],\n    \"threshold\": 2\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vault-roots\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"administrators\": [\n      \"2012cb90ca60e8e5d8daf66e2272d2233e0486d557e8c66141ed8920177d7eb7\",\n      \"c050c5637a44fa8629fff3cccce2300cb362a63d99d95fc54145266f4332445a\"\n    ],\n    \"threshold\": 2\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/vault-roots\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"administrators\": [\n            \"2012cb90ca60e8e5d8daf66e2272d2233e0486d557e8c66141ed8920177d7eb7\",\n            \"c050c5637a44fa8629fff3cccce2300cb362a63d99d95fc54145266f4332445a\",\n        ],\n        \"threshold\": 2,\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vault-roots/{root_id}/grants": {
      "post": {
        "operationId": "createVaultGrant",
        "tags": [
          "Vaults"
        ],
        "summary": "Store a grant signed by your administrators",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nA grant says who may approve, how many must, the cap of an operator and when it expires. Your\nadministrators sign it. The API verifies the signatures against the root before it stores the grant. A\nsignature from a key that is not an administrator, or one that repeats, is refused with the index of the\nsignature in `signatures`; a signature that does not verify counts as missing and ends as\n`GRANT_BELOW_THRESHOLD`. The enclave repeats every check.\n\n- `threshold` approvals are needed to release a transaction under this grant.\n- A grant is valid for **every** vault of its root: a vault is as strong as the weakest grant of its root.\n- Fields the API does not know are ignored and not stored.\n- An expired grant is refused with `422 GRANT_ALREADY_EXPIRED`. Fewer valid administrator signatures than\n  the root's quorum is `422 GRANT_BELOW_THRESHOLD`.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/RootId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VaultGrantRequest"
              },
              "examples": {
                "default": {
                  "summary": "A 2-of-2 grant with an Ed25519 and a P-256 approver",
                  "value": {
                    "approvers": [
                      {
                        "type": 2,
                        "public": "af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d",
                        "role": 2
                      },
                      {
                        "type": 3,
                        "public": "045b36890dacbd7c9a96bb74a1ee28b3d2d75b72e09a20ef25cf8e6fd8a9f0350d0e14bed8d4682a34d83538bdff5b96e89a6666ec0db5745d02fa1210072df75a",
                        "role": 2
                      }
                    ],
                    "threshold": 2,
                    "operator_cap": "1000000",
                    "cap_asset": {
                      "family": "evm",
                      "network": 8453,
                      "id": "833589fcd6edb6e08f4c7c32d4f71b54bda02913"
                    },
                    "valid_until": "2026-12-31T23:59:59.000Z",
                    "signatures": [
                      {
                        "public": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "signature": "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"
                      },
                      {
                        "public": "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
                        "signature": "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same grant already existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultGrant"
                }
              }
            }
          },
          "201": {
            "description": "The grant was stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultGrant"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vault-roots/{root_id}/grants \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"approvers\": [\n      {\n        \"type\": 2,\n        \"public\": \"af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d\",\n        \"role\": 2\n      },\n      {\n        \"type\": 3,\n        \"public\": \"045b36890dacbd7c9a96bb74a1ee28b3d2d75b72e09a20ef25cf8e6fd8a9f0350d0e14bed8d4682a34d83538bdff5b96e89a6666ec0db5745d02fa1210072df75a\",\n        \"role\": 2\n      }\n    ],\n    \"threshold\": 2,\n    \"operator_cap\": \"1000000\",\n    \"cap_asset\": {\n      \"family\": \"evm\",\n      \"network\": 8453,\n      \"id\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\"\n    },\n    \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n    \"signatures\": [\n      {\n        \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      },\n      {\n        \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      }\n    ]\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vault-roots/{root_id}/grants\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"approvers\": [\n      {\n        \"type\": 2,\n        \"public\": \"af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d\",\n        \"role\": 2\n      },\n      {\n        \"type\": 3,\n        \"public\": \"045b36890dacbd7c9a96bb74a1ee28b3d2d75b72e09a20ef25cf8e6fd8a9f0350d0e14bed8d4682a34d83538bdff5b96e89a6666ec0db5745d02fa1210072df75a\",\n        \"role\": 2\n      }\n    ],\n    \"threshold\": 2,\n    \"operator_cap\": \"1000000\",\n    \"cap_asset\": {\n      \"family\": \"evm\",\n      \"network\": 8453,\n      \"id\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\"\n    },\n    \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n    \"signatures\": [\n      {\n        \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      },\n      {\n        \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      }\n    ]\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/vault-roots/{root_id}/grants\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"approvers\": [\n            {\n                \"type\": 2,\n                \"public\": \"af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d\",\n                \"role\": 2,\n            },\n            {\n                \"type\": 3,\n                \"public\": \"045b36890dacbd7c9a96bb74a1ee28b3d2d75b72e09a20ef25cf8e6fd8a9f0350d0e14bed8d4682a34d83538bdff5b96e89a6666ec0db5745d02fa1210072df75a\",\n                \"role\": 2,\n            },\n        ],\n        \"threshold\": 2,\n        \"operator_cap\": \"1000000\",\n        \"cap_asset\": {\n            \"family\": \"evm\",\n            \"network\": 8453,\n            \"id\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\",\n        },\n        \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n        \"signatures\": [\n            {\n                \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n                \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n            },\n            {\n                \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n                \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n            },\n        ],\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vault-roots/{root_id}/grants/{grant_id}/revoke": {
      "post": {
        "operationId": "revokeVaultGrant",
        "tags": [
          "Vaults"
        ],
        "summary": "Revoke a grant",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nA revoked grant authorizes no new transaction and no new approval (`409 GRANT_REVOKED`). Transactions of the\ngrant that still wait for approval, and have no signature under way, are rejected with `GRANT_REVOKED`; a\nsignature that was already queued is cancelled. Repeating the call returns the original `revoked_at`.\n\n- `enforcement: \"api\"`: the API stops relaying. The enclave is not told, and an approval you already signed\n  is still valid there until its own deadline. Revoking inside the vault, on the spot, needs durable memory\n  in the enclave and is not built yet.\n- A signature the host already claimed is not stopped; it expires with the 15-minute limit of a stuck job.\n- It only removes power, so your API key is enough. To authorize again, sign a new grant with different content (for example another `valid_until`): the same content returns the revoked grant. A revocation is\n  not undone.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/RootId"
          },
          {
            "name": "grant_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The grant is revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "object": {
                      "type": "string",
                      "enum": [
                        "vault_grant"
                      ]
                    },
                    "root_id": {
                      "type": "string"
                    },
                    "revoked_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "rejected_transactions": {
                      "type": "integer"
                    },
                    "enforcement": {
                      "type": "string",
                      "enum": [
                        "api"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vault-roots/{root_id}/grants/{grant_id}/revoke \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vault-roots/{root_id}/grants/{grant_id}/revoke\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/vault-roots/{root_id}/grants/{grant_id}/revoke\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vaults": {
      "post": {
        "operationId": "createVault",
        "tags": [
          "Vaults"
        ],
        "summary": "Create a vault from a root and its rules",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nCreates a vault. The call answers `202`: the enclave creates the wallet asynchronously, so the vault starts\n`pending`. Poll `GET /vaults/{vault_id}` until it is `active` (or `failed`, with a `failure_code`).\n\n- `rules` is required and lists at least one destination: the API never creates an open vault.\n- `share_2_destination` and `share_3_destination` are the DER public keys that receive key shares 2 and 3,\n  sealed. They must be different keys, both yours.\n- An unknown field in the body is `400`, never ignored.\n- Send an `Idempotency-Key` to retry safely (see the header description).\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VaultRequest"
              },
              "examples": {
                "default": {
                  "summary": "A vault that can pay one address, capped per send",
                  "value": {
                    "root_id": "ozav_vrt_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "rules": {
                      "destinations": [
                        {
                          "family": "evm",
                          "address": "3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b"
                        }
                      ],
                      "per_send_cap": "5000000000",
                      "cap_asset": {
                        "family": "evm",
                        "id": "833589fcd6edb6e08f4c7c32d4f71b54bda02913"
                      }
                    },
                    "share_2_destination": {
                      "spki": "3059301306072a8648ce3d020106082a8648ce3d03010703420004f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6"
                    },
                    "share_3_destination": {
                      "spki": "3059301306072a8648ce3d020106082a8648ce3d0301070342000407070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707"
                    },
                    "root_authorization": {
                      "valid_until": "2026-12-31T23:59:59.000Z",
                      "signatures": [
                        {
                          "public": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                          "signature": "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"
                        },
                        {
                          "public": "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
                          "signature": "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The vault is `pending` and the enclave job is queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vault"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vaults \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"root_id\": \"ozav_vrt_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"rules\": {\n      \"destinations\": [\n        {\n          \"family\": \"evm\",\n          \"address\": \"3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b\"\n        }\n      ],\n      \"per_send_cap\": \"5000000000\",\n      \"cap_asset\": {\n        \"family\": \"evm\",\n        \"id\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\"\n      }\n    },\n    \"share_2_destination\": {\n      \"spki\": \"3059301306072a8648ce3d020106082a8648ce3d03010703420004f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\"\n    },\n    \"share_3_destination\": {\n      \"spki\": \"3059301306072a8648ce3d020106082a8648ce3d0301070342000407070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707\"\n    },\n    \"root_authorization\": {\n      \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n      \"signatures\": [\n        {\n          \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n          \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n        },\n        {\n          \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n          \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n        }\n      ]\n    }\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vaults\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"root_id\": \"ozav_vrt_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"rules\": {\n      \"destinations\": [\n        {\n          \"family\": \"evm\",\n          \"address\": \"3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b\"\n        }\n      ],\n      \"per_send_cap\": \"5000000000\",\n      \"cap_asset\": {\n        \"family\": \"evm\",\n        \"id\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\"\n      }\n    },\n    \"share_2_destination\": {\n      \"spki\": \"3059301306072a8648ce3d020106082a8648ce3d03010703420004f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\"\n    },\n    \"share_3_destination\": {\n      \"spki\": \"3059301306072a8648ce3d020106082a8648ce3d0301070342000407070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707\"\n    },\n    \"root_authorization\": {\n      \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n      \"signatures\": [\n        {\n          \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n          \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n        },\n        {\n          \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n          \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n        }\n      ]\n    }\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/vaults\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"root_id\": \"ozav_vrt_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"rules\": {\n            \"destinations\": [\n                {\n                    \"family\": \"evm\",\n                    \"address\": \"3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b\",\n                },\n            ],\n            \"per_send_cap\": \"5000000000\",\n            \"cap_asset\": {\n                \"family\": \"evm\",\n                \"id\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\",\n            },\n        },\n        \"share_2_destination\": {\n            \"spki\": \"3059301306072a8648ce3d020106082a8648ce3d03010703420004f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\",\n        },\n        \"share_3_destination\": {\n            \"spki\": \"3059301306072a8648ce3d020106082a8648ce3d0301070342000407070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707\",\n        },\n        \"root_authorization\": {\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"signatures\": [\n                {\n                    \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n                    \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n                },\n                {\n                    \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n                    \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n                },\n            ],\n        },\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vaults/{vault_id}": {
      "get": {
        "operationId": "getVault",
        "tags": [
          "Vaults"
        ],
        "summary": "Retrieve a vault",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\n`lineage`, `addresses`, `receipt` and `activated_at` appear only when `state` is `active`;\n`failure_code` only when it is `failed`. The receipt is the enclave's signed answer: verify it before you\ndeposit.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/VaultId"
          }
        ],
        "responses": {
          "200": {
            "description": "The vault.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vault"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vaults/{vault_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vaults/{vault_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/vaults/{vault_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vaults/{vault_id}/transactions": {
      "post": {
        "operationId": "createVaultTransaction",
        "tags": [
          "Vaults"
        ],
        "summary": "Describe a transfer and get the challenge to approve",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nDescribes one token transfer from an `active` vault: the exact transaction bytes, what they do (`intent`),\nthe derivation path, the grant it is approved under and how long approvers have. The answer carries the\n`challenge` each approver signs.\n\n- Families: `evm`, `solana` and `tron` (token transfers). Stellar transactions are not available in vaults.\n- `approve_until` is one deadline for every approver, up to 7 days ahead and not after the grant expires.\n- The `challenge` binds the vault, the grant, the sealed rules, the path and the transaction bytes. An\n  approval of one transaction is useless for any other.\n- `destination_grant_id` (optional) is a destination grant of this vault; with it the vault also accepts the\n  destinations that grant permits, until it expires.\n- `user_envelope` is your key share 3, sealed to your KMS key. OZAV passes it to the enclave and never opens it.\n- Send an `Idempotency-Key` to retry safely. It is scoped to the vault.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/VaultId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyOptional"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VaultTransactionRequest"
              },
              "examples": {
                "default": {
                  "summary": "An ERC-20 transfer on Base",
                  "value": {
                    "grant_id": "ozav_vgr_01J8Z9K2M3N4P5Q6R7S8T9U0V1",
                    "family": "evm",
                    "derivation_path": "m/44'/60'/0'/0/0",
                    "raw_transaction": "02f811111111111111111111111111111111111111111111111111111111111111111111111111111111",
                    "intent": {
                      "chain_id": 8453,
                      "token": "833589fcd6edb6e08f4c7c32d4f71b54bda02913",
                      "destination": "3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b",
                      "amount": "1000000",
                      "fee_cap_wei": "5000000000000000"
                    },
                    "user_envelope": {
                      "dominio": 3,
                      "forma": "kms",
                      "ciphertextBlob": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                      "keyId": "1234abcd-12ab-34cd-56ef-1234567890ab",
                      "algoritmo": "RSAES_OAEP_SHA_256"
                    },
                    "approve_until": "2026-11-02T18:00:00.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The transaction awaits approvals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultTransaction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vaults/{vault_id}/transactions \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Idempotency-Key: idem-6f9c24e5b1a74d38\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"grant_id\": \"ozav_vgr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"family\": \"evm\",\n    \"derivation_path\": \"m/44'/60'/0'/0/0\",\n    \"raw_transaction\": \"02f811111111111111111111111111111111111111111111111111111111111111111111111111111111\",\n    \"intent\": {\n      \"chain_id\": 8453,\n      \"token\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\",\n      \"destination\": \"3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b\",\n      \"amount\": \"1000000\",\n      \"fee_cap_wei\": \"5000000000000000\"\n    },\n    \"user_envelope\": {\n      \"dominio\": 3,\n      \"forma\": \"kms\",\n      \"ciphertextBlob\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"keyId\": \"1234abcd-12ab-34cd-56ef-1234567890ab\",\n      \"algoritmo\": \"RSAES_OAEP_SHA_256\"\n    },\n    \"approve_until\": \"2026-11-02T18:00:00.000Z\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vaults/{vault_id}/transactions\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"grant_id\": \"ozav_vgr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n    \"family\": \"evm\",\n    \"derivation_path\": \"m/44'/60'/0'/0/0\",\n    \"raw_transaction\": \"02f811111111111111111111111111111111111111111111111111111111111111111111111111111111\",\n    \"intent\": {\n      \"chain_id\": 8453,\n      \"token\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\",\n      \"destination\": \"3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b\",\n      \"amount\": \"1000000\",\n      \"fee_cap_wei\": \"5000000000000000\"\n    },\n    \"user_envelope\": {\n      \"dominio\": 3,\n      \"forma\": \"kms\",\n      \"ciphertextBlob\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"keyId\": \"1234abcd-12ab-34cd-56ef-1234567890ab\",\n      \"algoritmo\": \"RSAES_OAEP_SHA_256\"\n    },\n    \"approve_until\": \"2026-11-02T18:00:00.000Z\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/vaults/{vault_id}/transactions\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Idempotency-Key\": \"idem-6f9c24e5b1a74d38\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"grant_id\": \"ozav_vgr_01J8Z9K2M3N4P5Q6R7S8T9U0V1\",\n        \"family\": \"evm\",\n        \"derivation_path\": \"m/44'/60'/0'/0/0\",\n        \"raw_transaction\": \"02f811111111111111111111111111111111111111111111111111111111111111111111111111111111\",\n        \"intent\": {\n            \"chain_id\": 8453,\n            \"token\": \"833589fcd6edb6e08f4c7c32d4f71b54bda02913\",\n            \"destination\": \"3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b3b\",\n            \"amount\": \"1000000\",\n            \"fee_cap_wei\": \"5000000000000000\",\n        },\n        \"user_envelope\": {\n            \"dominio\": 3,\n            \"forma\": \"kms\",\n            \"ciphertextBlob\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n            \"keyId\": \"1234abcd-12ab-34cd-56ef-1234567890ab\",\n            \"algoritmo\": \"RSAES_OAEP_SHA_256\",\n        },\n        \"approve_until\": \"2026-11-02T18:00:00.000Z\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vaults/{vault_id}/transactions/{transaction_id}": {
      "get": {
        "operationId": "getVaultTransaction",
        "tags": [
          "Vaults"
        ],
        "summary": "Retrieve a vault transaction",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\n`status` is `awaiting_approvals`, `signed` or `rejected` (`submitted` and `expired` are reserved and never returned today). A `rejected` transaction carries a `failure_code`: `GRANT_REVOKED` when its grant was revoked, `HOST_TIMEOUT` when the signature was claimed and not completed within 15 minutes, or the code the vault refused with. Create the transaction again to retry. `signing` is true while the quorum is met\nand the enclave has not answered yet. `signed_transaction` appears only when `status` is `signed`, and\n`failure_code` only when it is `rejected`. OZAV does not broadcast: you do.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/VaultId"
          },
          {
            "$ref": "#/components/parameters/TransactionId"
          }
        ],
        "responses": {
          "200": {
            "description": "The transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultTransaction"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vaults/{vault_id}/transactions/{transaction_id} \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vaults/{vault_id}/transactions/{transaction_id}\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/vaults/{vault_id}/transactions/{transaction_id}\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vaults/{vault_id}/transactions/{transaction_id}/approvals": {
      "post": {
        "operationId": "approveVaultTransaction",
        "tags": [
          "Vaults"
        ],
        "summary": "Approve a transaction with one of your approver keys",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nOne call per approver. Sign the transaction's `challenge` with the approver key and send the signature.\n\n- `ed25519`: sign the 32 challenge bytes. `p256`: ECDSA over SHA-256 of the challenge, DER signature (a cloud\n  KMS signs the raw bytes without hashing them again). The API checks both signatures against the challenge.\n- `passkey`: send `auth_data`, `client_data_json` and the DER `signature`. The API stores the assertion and\n  the enclave verifies it.\n- `valid_until` must be the transaction's `approve_until`.\n- The key must be an approver of the grant the transaction was created under (`422 APPROVER_NOT_IN_GRANT`).\n- The same approver twice counts once and answers `200`.\n- When the grant's quorum is met the vault signs. The enclave repeats every check, including the cap of an\n  operator role, and may still reject the transaction.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/VaultId"
          },
          {
            "$ref": "#/components/parameters/TransactionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VaultApprovalRequest"
              },
              "examples": {
                "default": {
                  "summary": "An Ed25519 approval of the transaction challenge",
                  "value": {
                    "type": "ed25519",
                    "public": "af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d",
                    "signature": "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5",
                    "valid_until": "2026-11-02T18:00:00.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "This approver had already approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultTransaction"
                }
              }
            }
          },
          "201": {
            "description": "The approval was recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultTransaction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vaults/{vault_id}/transactions/{transaction_id}/approvals \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"type\": \"ed25519\",\n    \"public\": \"af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d\",\n    \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n    \"valid_until\": \"2026-11-02T18:00:00.000Z\"\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vaults/{vault_id}/transactions/{transaction_id}/approvals\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"type\": \"ed25519\",\n    \"public\": \"af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d\",\n    \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n    \"valid_until\": \"2026-11-02T18:00:00.000Z\"\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/vaults/{vault_id}/transactions/{transaction_id}/approvals\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"type\": \"ed25519\",\n        \"public\": \"af06a3e3291714e4f356c19c9b15cd1951ec6e6662aa77be07547f289383341d\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n        \"valid_until\": \"2026-11-02T18:00:00.000Z\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vaults/{vault_id}/destination-grants": {
      "post": {
        "operationId": "createVaultDestinationGrant",
        "tags": [
          "Vaults"
        ],
        "summary": "Permit new destinations on a vault, signed by your administrators",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nThe vault's allowed destinations are sealed when it is created. To pay a new address without recreating the\nvault, your administrators sign a list of destinations with an expiry. The signed message binds the vault, its\nsealed rules, the destinations and the expiry, so a grant for one vault is useless on another. The API checks the\nadministrator quorum of the vault's root before it stores anything; the enclave repeats every check.\n\n- A transaction uses the grant by sending its id as `destination_grant_id`. The grant must outlast the\n  transaction's `approve_until`.\n- **Revoking is letting it expire**, or issuing a new grant without the destination. The worst delay is the\n  time left on the grant that is still valid, so prefer short expiries.\n- The API does not check that an address is the right one. Confirm it through a second channel before your\n  administrators sign.\n- The same destinations and expiry on the same vault return the existing grant with `200`.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:write"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/VaultId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VaultDestinationGrantRequest"
              },
              "examples": {
                "default": {
                  "summary": "Permit one more address for 30 days",
                  "value": {
                    "destinations": [
                      {
                        "family": "evm",
                        "address": "4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c"
                      }
                    ],
                    "valid_until": "2026-12-01T00:00:00.000Z",
                    "signatures": [
                      {
                        "public": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
                        "signature": "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"
                      },
                      {
                        "public": "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
                        "signature": "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same destination grant already existed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultDestinationGrant"
                }
              }
            }
          },
          "201": {
            "description": "The destination grant was stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultDestinationGrant"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vaults/{vault_id}/destination-grants \\\n  -X POST \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"destinations\": [\n      {\n        \"family\": \"evm\",\n        \"address\": \"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c\"\n      }\n    ],\n    \"valid_until\": \"2026-12-01T00:00:00.000Z\",\n    \"signatures\": [\n      {\n        \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      },\n      {\n        \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      }\n    ]\n  }'"
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vaults/{vault_id}/destination-grants\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"destinations\": [\n      {\n        \"family\": \"evm\",\n        \"address\": \"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c\"\n      }\n    ],\n    \"valid_until\": \"2026-12-01T00:00:00.000Z\",\n    \"signatures\": [\n      {\n        \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      },\n      {\n        \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n        \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\"\n      }\n    ]\n  }),\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://developers.ozav.io/v2/vaults/{vault_id}/destination-grants\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n        \"Content-Type\": \"application/json\",\n    },\n    json={\n        \"destinations\": [\n            {\n                \"family\": \"evm\",\n                \"address\": \"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c\",\n            },\n        ],\n        \"valid_until\": \"2026-12-01T00:00:00.000Z\",\n        \"signatures\": [\n            {\n                \"public\": \"a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\",\n                \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n            },\n            {\n                \"public\": \"b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\",\n                \"signature\": \"e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\",\n            },\n        ],\n    },\n)\ndata = response.json()"
          }
        ]
      }
    },
    "/vaults/attestation": {
      "get": {
        "operationId": "getVaultAttestation",
        "tags": [
          "Vaults"
        ],
        "summary": "Retrieve the enclave attestation",
        "description": "Closed (`coming_soon`): this call answers `503 WAAS_VAULTS_NOT_ENABLED` until vaults are enabled for the platform.\n\nThe attestation document of the enclave that runs your vaults: a COSE_Sign1 signed by the certificate chain of the enclave platform, with the\nPCR0 of the running image and a timestamp inside. Verify it yourself before you trust a vault: the chain up to the\nplatform root certificate, the PCR0 you trust, and that the timestamp is recent.\n\n- OZAV **relays** the document, it does not attest anything: the host publishes it every few minutes and this\n  route returns the latest. Whoever relays it can delay it but cannot forge it.\n- `observed_at` is when OZAV received it, not proof of freshness. Check the timestamp **inside** the document.\n- The same document for every customer: it describes the enclave, which all vaults share.\n- `404 VAULT_ATTESTATION_NOT_AVAILABLE` until the host has published one.\n",
        "x-ozav-entitlement": "self_custody",
        "x-ozav-scopes": [
          "wallets:read"
        ],
        "x-ozav-availability": "coming_soon",
        "x-ozav-services": true,
        "responses": {
          "200": {
            "description": "The latest attestation document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaultAttestation"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/VaultsNotEnabled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl https://developers.ozav.io/v2/vaults/attestation \\\n  -H \"X-API-Key: ozav_sk_test_0123456789abcdef\""
          },
          {
            "lang": "JavaScript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch(\"https://developers.ozav.io/v2/vaults/attestation\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n  },\n});\nconst data = await response.json();"
          },
          {
            "lang": "Python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://developers.ozav.io/v2/vaults/attestation\",\n    headers={\n        \"X-API-Key\": \"ozav_sk_test_0123456789abcdef\",\n    },\n)\ndata = response.json()"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Secret key (ozav_sk_test_* or ozav_sk_live_*)."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The same secret key presented as a bearer token."
      }
    },
    "parameters": {
      "CustomerId": {
        "name": "customer_id",
        "in": "path",
        "required": true,
        "description": "The opaque customer id (`ozav_cus_...`).",
        "schema": {
          "type": "string"
        }
      },
      "DocumentId": {
        "name": "document_id",
        "in": "path",
        "required": true,
        "description": "The opaque supporting-document id (`ozav_doc_...`).",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Page size.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      },
      "StartingAfter": {
        "name": "starting_after",
        "in": "query",
        "description": "Opaque cursor returned as `next_cursor` on the previous page.",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKeyRequired": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Client-generated unique key: 16-256 characters of `[A-Za-z0-9_-]`.\nSame key + same body replays the original\nresponse (`Idempotent-Replayed: true`); same key + different body\nreturns 409 IDEMPOTENCY_KEY_REUSED; a concurrent duplicate returns 202.\n",
        "schema": {
          "type": "string",
          "minLength": 16,
          "maxLength": 256,
          "pattern": "^[A-Za-z0-9_-]{16,256}$"
        }
      },
      "X402PaymentSignature": {
        "name": "PAYMENT-SIGNATURE",
        "in": "header",
        "required": false,
        "description": "Pay this call in USDC with x402 (v2, scheme `exact`) instead of prepaid credit: the base64 `PaymentPayload` answering the `PAYMENT-REQUIRED` challenge of a previous `402`. OZAV checks the authorization itself, settles it on-chain itself, credits the amount to your prepaid balance and runs the call, which is then charged as usual; the settlement comes back in the `PAYMENT-RESPONSE` header. A call that fails after payment is refunded to your credit, not on-chain. To pay with MPP instead, send `Authorization: Payment <credential>` answering the `WWW-Authenticate` challenge, with your API key in `X-API-Key`; the receipt returns in `Payment-Receipt`, and one authorization is credited once whichever protocol carries it. With a test key, see `X-Ozav-Simulate-Payment`.\n",
        "schema": {
          "type": "string",
          "maxLength": 16384
        }
      },
      "XOzavSimulatePayment": {
        "name": "X-Ozav-Simulate-Payment",
        "in": "header",
        "required": false,
        "description": "Sandbox only. A sandbox call is never charged, so it never answers `402` by itself. Send `required` to rehearse the payment: the call answers the same `402` (`PAYMENT-REQUIRED` and `details.x402`, with `details.simulated: true`) at the real price of the call, payable to the sandbox address `0x0000000000000000000000000000000000000402`. Retry with a well-formed `PAYMENT-SIGNATURE` (for example the one an OZAV agent signs in the sandbox with `POST /agents/{agent_id}/payments`): the call runs and `PAYMENT-RESPONSE` comes back with `simulated: true`. Nothing is checked on-chain and nothing moves. Ignored with a live key.\n",
        "schema": {
          "type": "string",
          "enum": [
            "required"
          ]
        }
      },
      "IdempotencyKeyOptional": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optional idempotency key (16-256 characters of `[A-Za-z0-9_-]`); same semantics as on money-moving operations.\n",
        "schema": {
          "type": "string",
          "minLength": 16,
          "maxLength": 256,
          "pattern": "^[A-Za-z0-9_-]{16,256}$"
        }
      },
      "RootId": {
        "name": "root_id",
        "in": "path",
        "required": true,
        "description": "The vault root id (`ozav_vrt_...`).",
        "schema": {
          "type": "string"
        }
      },
      "VaultId": {
        "name": "vault_id",
        "in": "path",
        "required": true,
        "description": "The vault id (`ozav_vlt_...`).",
        "schema": {
          "type": "string"
        }
      },
      "TransactionId": {
        "name": "transaction_id",
        "in": "path",
        "required": true,
        "description": "The vault transaction id (`ozav_vtx_...`).",
        "schema": {
          "type": "string"
        }
      }
    },
    "headers": {
      "IdempotentReplayed": {
        "description": "Present and `true` when this response is a replay of an earlier request that carried the same `Idempotency-Key` and the same body. The status code and body are byte-for-byte what the first call returned, and nothing happened a second time. Absent on a first execution. Use it to tell \"I created it\" from \"it already existed\", which the status code alone does not tell you.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "XRequestId": {
        "description": "Correlation id for this exact request. Set on every response, success or error. On an error it is also in the body (`error.request_id`); on a success the body carries no id, so this header is the only place it exists. Log it. It is the first thing OZAV support asks for, and a generated client that ignores response headers throws it away.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is invalid.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The request is well-formed but cannot be fulfilled - e.g. a `rail` that is not served (see `Rail.x-ozav-rail-availability`).\n",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The organization lacks the entitlement (code ENTITLEMENT_REQUIRED) or\nthe key lacks the scope (code INSUFFICIENT_SCOPE). On money-moving\nrequests (POST /transfers, POST /wallets/{wallet_id}/transfers) a\ncompliance hold on the customer answers with code COMPLIANCE_HOLD: the\nhold is reviewed and lifted on OZAV's side; retrying does not clear it.\nWhen the only hold is a pause until pending onboarding documents are\nreceived, a withdrawal answers CUSTOMER_DOCUMENTS_PENDING instead: send\nwhat `requirements` on GET /customers/{id} lists; OZAV lifts the pause\nafter reviewing it.\nOn OZAV Services (live without the platform's KYB), any API outside decisions,\nscreenings, lists and self-custody wallets answers with code PRODUCTION_NOT_ACTIVATED.\n",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource does not exist, is not visible to the caller, or the id was\nmalformed for this position (see \"Resource ids\" under Errors). These share\none status; `code` and `message` tell them apart.",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict (e.g. IDEMPOTENCY_KEY_REUSED).",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "IdempotencyProcessing": {
        "description": "Accepted - a concurrent request with the same `Idempotency-Key` is still executing; the body is `{\"status\": \"processing\"}`. Retry with the same key once the original completes: a completed request replays its stored response verbatim with the original status code and the `Idempotent-Replayed: true` header. This is not the resource - check the status code before deserializing.\n\nIf this `202` repeats instead of resolving, the original request's outcome is unknown and no stored result will arrive - the key stays claimed when a request fails after the resource may already have been written. Stop retrying, reconcile with a `GET` to see whether the resource exists, and use a new key if it does not.\n",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "status"
              ],
              "properties": {
                "status": {
                  "type": "string",
                  "const": "processing"
                }
              }
            },
            "examples": {
              "in_flight": {
                "summary": "Same Idempotency-Key still executing - retry with the same key",
                "value": {
                  "status": "processing"
                }
              }
            }
          }
        }
      },
      "NotImplementedYet": {
        "description": "The route exists and validated your request, but the capability behind it is not wired yet - a build-ahead seam, not a client error. Nothing was created. Do not retry on a loop; check `GET /capabilities` for go-live.\n",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "OZAV Services only (prepaid): your prepaid credit does not cover this call (code\nINSUFFICIENT_CREDIT). The call did not run and nothing was charged. `details` carries\n`balance_usd` and `price_usd`; top up in the portal (Billing) and retry. Organizations on\nthe OZAV platform are invoiced monthly and never receive this.\n\nOn `POST /decisions` and `POST /screenings`, where OZAV accepts x402 payment, the response also\ncarries an x402 v2 challenge: the `PAYMENT-REQUIRED` header (base64 `PaymentRequired`) and the\nsame object in `details.x402` - the price of this one call in native USDC, paid to OZAV. `accepts`\nlists one entry per network OZAV receives on, Polygon (`eip155:137`) first; where enabled, a\nsecond entry offers Base (`eip155:8453`). Pay on any one of them, signing for that network's USDC\n(the entry's `asset`); a payment whose `accepted.asset` is not the USDC of its `network` is refused\nwith `network_or_asset_not_accepted`. Retry with the `PAYMENT-SIGNATURE` header to pay it. A payment OZAV does not accept answers\n`402 X402_PAYMENT_REJECTED` with `details.reason` (`amount_below_price`, `wrong_pay_to`,\n`network_or_asset_not_accepted`, `authorization_not_yet_valid`, `authorization_expires_too_soon`,\n`authorization_window_too_long`, `signature_invalid`, `authorization_already_used`,\n`insufficient_balance`, `settlement_refused`, `payment_malformed` or `payment_not_accepted`) and a\nnew challenge; nothing was charged. If OZAV could not confirm the settlement, `503\nX402_PAYMENT_UNAVAILABLE`: retry with the same header, it is never charged twice. A retried payment is\ncredited only once OZAV reads, on-chain, the transaction that used the authorization moving the USDC to OZAV; when that cannot be\nestablished automatically, the `503` says the payment needs a manual check (contact support with the request id).\n\nWhere OZAV also accepts MPP, the response carries an MPP challenge in `WWW-Authenticate: Payment`\n(method `evm`, intent `charge`, credential type `authorization`, valid for five minutes) and in\n`details.mpp.www_authenticate`. A credential OZAV does not accept answers `402\nMPP_PAYMENT_REJECTED` with `details.reason` (the reasons above, plus\n`challenge_not_issued_by_ozav`, `challenge_expired` and `nonce_not_bound_to_challenge`); `503\nMPP_PAYMENT_UNAVAILABLE` means retry with the same credential.\n",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          },
          "PAYMENT-REQUIRED": {
            "description": "The x402 v2 challenge (base64 JSON), only where OZAV accepts x402 for the call.",
            "schema": {
              "type": "string"
            }
          },
          "WWW-Authenticate": {
            "description": "The MPP challenge (`Payment id=..., realm=..., method=\"evm\", intent=\"charge\", request=..., expires=...`), only where OZAV accepts MPP for the call.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "DependencyUnavailable": {
        "description": "A dependency OZAV needs (storage, a resource lookup) could not be reached, so OZAV fails closed rather than guess. Safe to retry with the same `Idempotency-Key` where the operation accepts one.\n",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Read `code` first. `RATE_LIMIT_EXCEEDED` is the per-minute limit:\nwait the number of seconds in the `Retry-After` response header before retrying;\nretrying sooner just consumes budget without being served. A cap with its own code\n(for example `CREDENTIAL_ISSUANCE_LIMIT_REACHED`, which resets the next day) carries\nno `Retry-After`, and retrying sooner than its stated reset cannot succeed.\n",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying. Among `429` responses, sent with `RATE_LIMIT_EXCEEDED` only.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "VaultsNotEnabled": {
        "description": "`WAAS_VAULTS_NOT_ENABLED` - vaults are off for this platform - or `VAULT_STORE_UNAVAILABLE`, a transient\nstorage failure. Retry the second shortly.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "NetworkTransfer": {
        "type": "object",
        "description": "One network transfer of your own balance.\n\n`status` is smaller than the internal state machine. There is\nno \"expired\": internally that means the attestation window passed and will\nbe requested again, and showing the word to a client would say the money\nwas lost about a transfer that is going fine.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "submitted",
              "refused",
              "failed",
              "indeterminate"
            ],
            "description": "Six states, and the difference between them is money, so it is\nstated one by one. This mirrors `YieldApplication.status`.\n\n`pending` - accepted and queued. Nothing has been attempted yet.\n\n`running` - being attempted right now. Wait; do not order again.\n\n`submitted` - it left the source network and is on its way. This is\nnot a failure and not a completion: the arrival is confirmed on the\ndestination network, and this field does not yet report it.\n\n`refused` - a gate declined it and no money left your balance.\n`detail` says which gate. Fixing what it names and ordering again is\nsafe.\n\n`failed` - it was attempted and nothing left your balance, and that\nis known. Safe to order again.\n\n`indeterminate` - it stopped without knowing whether the amount left\nyour balance. Do not order it again: a second attempt may move the\namount twice, and moving between networks cannot be undone. Support\nhas been notified and a person confirms it on the network.\n"
          },
          "source_chain": {
            "type": "string"
          },
          "destination_chain": {
            "type": "string"
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "Decimal integer string, smallest unit of `asset`."
          },
          "spread_bps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The spread rate that applies to this transfer, in integer basis\npoints, frozen when it was accepted. It is the rate, not an amount\ndeducted: the transfer moves the full `amount` you requested. Null until then. The network cost is a separate line and is\nnever folded into this.\n"
          },
          "delivery": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "in_progress",
              "completed",
              "failed",
              null
            ],
            "description": "Whether the amount arrived on the destination network. This is a\ndifferent question from `status`, and both matter.\n\n`status` is about the order: accepted, running, submitted, refused,\nfailed, indeterminate. It stops at `submitted` once the amount\nleaves the source network, and stays there.\n\n`delivery` is about the money: `in_progress` while it is crossing,\n`completed` once it is credited on the destination network, and\n`failed` if the crossing ended without crediting.\n\n`null` means there is no crossing yet: the order has not left.\nIt does not mean the amount failed to arrive.\n"
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why, for `refused`, `failed` and `indeterminate`. Null otherwise.\nFor `indeterminate` it carries the do-not-retry text.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "YieldApplication": {
        "type": "object",
        "description": "An instruction to apply custodied balance to a yield strategy. It is\nwhat the client asked for, not what the client holds. The position\nactually held is read from `GET /yield/positions`, which is built from\nwhat happened on-chain. Confusing the two would make a refused\ninstruction look like a position.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "applied",
              "refused",
              "failed",
              "indeterminate"
            ],
            "description": "Three of these are terminal and two of them are safe to retry. The\ndifference is money, so it is stated field by field.\n\n`applied` - confirmed on-chain. `tx_hash` carries the transaction.\n\n`refused` - a gate declined it and no money moved. `detail` says\nwhich gate. Fixing what it names and ordering again is safe.\n\n`failed` - it was attempted and nothing moved, and that is known:\neither the network rejected the transaction, or it never left. Safe\nto order again.\n\n`indeterminate` - it stopped without knowing whether the money\nmoved. Either an internal approval is still pending, or a\ntransaction went out and was not yet recorded. Do not order it\nagain: a second order can move the balance twice. While an\napplication sits here, further orders for the same strategy answer\n202 with `duplicate: true` and this same application. It clears\nonly after a person checks the chain. `tx_hash` is populated\nwhenever a transaction is known, and it is the pointer to use.\n\n`pending` / `running` - queued, and being worked on.\n"
          },
          "strategy_id": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "Smallest unit, as a decimal integer string."
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "The cause, for `refused` and `failed`."
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present only when a transaction existed; null for `refused`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "YieldRedemptionOrder": {
        "type": "object",
        "description": "An instruction to take custodied balance back out of a yield strategy.\nLike `YieldApplication`, it is what was asked for, not what is held: the\nposition is read from `GET /yield/positions`, and the realized gain from\n`GET /yield/realized-gains`, both built from what happened on-chain.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "redeemed",
              "refused",
              "failed",
              "indeterminate"
            ],
            "description": "The same states as `YieldApplication.status`, with `redeemed` in\nplace of `applied`.\n\n`redeemed` - confirmed on-chain; the balance is back on your custody\naddress. `tx_hash` carries the transaction.\n\n`refused` - a gate declined it and no money moved. `detail` says\nwhich gate. Fixing what it names and ordering again is safe.\n\n`failed` - it was attempted and nothing moved, and that is known.\nSafe to order again.\n\n`indeterminate` - it stopped without knowing whether the money\nmoved. Do not order it again. Further orders for the same strategy,\nin either direction, wait on it. It clears only after a person\nchecks the chain; `tx_hash` is the pointer when a transaction is\nknown.\n\n`pending` / `running` - queued, and being worked on.\n"
          },
          "strategy_id": {
            "type": "string"
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Smallest unit, as a decimal integer string. Null when `all` is true."
          },
          "all": {
            "type": "boolean",
            "description": "True when every share held was redeemed."
          },
          "idempotency_key": {
            "type": "string"
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "The cause, for `refused`, `failed` and `indeterminate`."
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present whenever a transaction is known; null for `refused`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "NetworkCost": {
        "type": "object",
        "description": "What the blockchain network charges to carry out an operation. A pass\nthrough cost, not a charge by OZAV, and it is quoted on its own line.\n\nReported at six decimal places when two would round a real cost to\n\"0.00\": a fraction of a cent on a cheap network is a real number, and\nrounding it to zero makes the same false claim that stating no cost\nwould.\n",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "type": "string",
            "description": "Decimal string. Never a float.",
            "examples": [
              "0.60",
              "0.003215"
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ],
            "description": "The currency `amount` is quoted in. Always USD, including when the network charges in its own token - the token cost is converted so the line is comparable."
          }
        }
      },
      "YieldRedemption": {
        "type": "object",
        "description": "What can leave a yield position right now, discriminated by cause.\n\n`balance` says what the position is worth; this says what the venue will\nrelease. The two diverge when the underlying market is fully utilised,\nand the holder needs to know before requesting a withdrawal rather than\nat the point of refusal.\n\nThe three causes call for opposite actions, so they are never collapsed:\n`held` means the funds are there and still earning (wait), `unreadable`\nmeans the limit could not be read (a defect to fix), and only\n`available` states a redeemable amount.\n",
        "required": [
          "kind",
          "available_to_withdraw"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "available",
              "held",
              "unreadable"
            ],
            "description": "Why the position is in this state, and the three call for opposite actions. Branch on\nthis before reading `available_to_withdraw`.\n\n`available` - a redeemable amount is stated. `held` - the funds are there and still\nearning, but the venue will not release them now; wait and re-read. `unreadable` - the\nlimit could not be read, which is a defect on our side, not a statement about your\nfunds; do not present it as zero.\n"
          },
          "available_to_withdraw": {
            "type": [
              "string",
              "null"
            ],
            "description": "Smallest-unit amount, as a string. `\"0\"` when `kind` is `held`, which\nis a measured zero. `null` when `kind` is `unreadable`, which is not\na zero at all: it means the limit was never read.\n"
          }
        },
        "examples": [
          {
            "kind": "available",
            "available_to_withdraw": "42180550"
          },
          {
            "kind": "held",
            "available_to_withdraw": "0"
          },
          {
            "kind": "unreadable",
            "available_to_withdraw": null
          }
        ]
      },
      "YieldRisk": {
        "type": "object",
        "description": "What can go wrong with a strategy, stated as shape rather than as names.\n\nOZAV never names the protocol, the curator, the issuer, or the contract\naddress on a client surface: you contract with OZAV, not with the venue.\nThat rule left a real gap, because without any of those you also could\nnot tell two strategies apart on risk. This object closes it: it answers\nthe questions that change a decision, and none that identify a party.\n\n`measured_at` is when these facts were read on-chain. A risk profile with\nno date ages without anyone noticing.\n",
        "required": [
          "management",
          "curator_ref",
          "issuer_freeze",
          "redemption_form",
          "principal_guaranteed",
          "smart_contract_risk",
          "measured_at"
        ],
        "properties": {
          "management": {
            "type": "string",
            "enum": [
              "direct",
              "curated"
            ],
            "description": "Whether a team selects the underlying markets (`curated`) or nothing\nsits between the holder and the market (`direct`).\n"
          },
          "curator_ref": {
            "type": [
              "string",
              "null"
            ],
            "description": "An opaque, stable label for the curator; `null` when `management` is\n`direct`. It is not a name and never becomes one. It answers exactly\none question: do two strategies share the team that decides? That is\nthe concentration question, and you can answer it without knowing who\nthe team is. Two strategies with the same value concentrate the same\ndecision, however different their networks look.\n"
          },
          "issuer_freeze": {
            "type": "string",
            "enum": [
              "none",
              "contract_rule",
              "unilateral_key"
            ],
            "description": "Whether the instrument's issuer can freeze the position, and how.\n`none`: no such authority exists. `contract_rule`: it exists and runs\nthrough public, auditable logic on-chain. `unilateral_key`: it exists\nas a key, and the issuer can freeze any account on its own.\n\nThe same instrument can carry a different form on different networks.\nWhen it does, the two are listed as separate strategies and this field\nis what tells them apart.\n"
          },
          "redemption_form": {
            "type": "string",
            "enum": [
              "immediate",
              "by_request"
            ],
            "description": "The form of redemption the instrument supports.\n`immediate` redeems against the contract, up to what it releases.\n`by_request` is a request to the issuer, with its own turnaround, and\nnever settles on the spot.\n\nThis is not today's liquidity. How much can leave right now is on the\nposition, read live from the chain; a calm day never turns\n`by_request` into `immediate`.\n"
          },
          "principal_guaranteed": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Always `false`. OZAV does not promise, guarantee, or indicate a\nreturn, and does not repurchase or restore principal. The result is\nwhatever the protocol produces, positive or negative.\n"
          },
          "smart_contract_risk": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Always `true`. The balance sits in a contract, and a contract can\nfail or be exploited. Admission checks reduce this risk and do not\nremove it.\n"
          },
          "measured_at": {
            "type": "string",
            "format": "date",
            "description": "When these facts were read on-chain, `YYYY-MM-DD`."
          }
        }
      },
      "RealizedApy": {
        "type": "object",
        "description": "A measured rate, derived from the on-chain share price recorded hourly.\nDistinct from the indicative rate, which is refreshed off-chain and\nstates what the venue pays now. Both are published; neither replaces the\nother.\n\nWhen the rate cannot be stated, `known` is `false` and `reason` names the\ncause instead of the field being null or zero.\n",
        "required": [
          "known"
        ],
        "properties": {
          "known": {
            "type": "boolean",
            "description": "Whether a rate could be measured at all. Branch on this first - when it is `false` there is no `bps` and `reason` names the cause. An unknown rate is not zero."
          },
          "bps": {
            "type": "integer",
            "description": "Present only when `known` is true."
          },
          "observed_hours": {
            "type": "integer",
            "description": "How much time the readings actually span. Present only when `known`\nis true. The `7d`/`30d` in the field name is what was requested; this\nis what was measured.\n"
          },
          "points": {
            "type": "integer",
            "description": "Readings used. Present only when `known` is true."
          },
          "reason": {
            "type": "string",
            "enum": [
              "no_data",
              "window_not_covered",
              "window_too_short",
              "unusable_price",
              "price_went_backwards"
            ],
            "description": "Present only when `known` is false."
          }
        },
        "examples": [
          {
            "known": true,
            "bps": 512,
            "observed_hours": 167,
            "points": 168
          },
          {
            "known": false,
            "reason": "window_not_covered"
          }
        ]
      },
      "DecimalAmount": {
        "type": "string",
        "description": "Decimal string at accounting precision - up to 6 fractional digits.\nNever a float. The value must be greater than zero, with no leading\nzeros in the integer part (a bare `0` integer part is allowed for\nsub-unit values such as \"0.50\") and at most 15 integer digits.\n",
        "pattern": "^(0|[1-9]\\d{0,14})(\\.\\d{1,6})?$",
        "examples": [
          "1250.00",
          "0.50"
        ]
      },
      "TransferDecimalAmount": {
        "type": "string",
        "description": "Decimal string accepted on transfer creation - up to 8 fractional\ndigits. Never a float.\n",
        "pattern": "^\\d+(\\.\\d{1,8})?$",
        "examples": [
          "1250.00",
          "0.12345678"
        ]
      },
      "Currency": {
        "type": "string",
        "description": "Uppercase ISO 4217 code or stablecoin symbol (e.g. BRL, USD, EUR, MXN, USDC, USDT). Uppercase is the canonical form: values are accepted case-insensitively on input but are always uppercase in responses.\n",
        "pattern": "^[A-Z]{3,6}$"
      },
      "Transaction": {
        "type": "object",
        "description": "Amounts use the same scale for both `type`s: a decimal string at the currency's human precision. A deposit is stored on-chain as an integer in the asset's smallest unit and is converted here, so `15.750742` never appears as `15750742` next to a transfer's `97.117228`. One movement in the unified feed. `type` tells you which resource it came from, and `id` is that resource's id - fetch the full record with it.",
        "required": [
          "object",
          "id",
          "type",
          "status",
          "amount",
          "currency",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "transaction",
            "description": "Resource type discriminator for the envelope; always the literal `transaction`. What kind of movement it is lives in `type`."
          },
          "id": {
            "type": "string",
            "description": "The underlying resource's id. For `transfer` it is the `ozav_tr_` id; for `deposit` it is the custody deposit id. Opaque either way - store it and pass it back, never parse it."
          },
          "type": {
            "type": "string",
            "enum": [
              "transfer",
              "deposit"
            ],
            "description": "`transfer` - a movement you created through this API. `deposit` - an on-chain credit observed into custody."
          },
          "status": {
            "type": "string",
            "description": "The underlying resource's status, passed through unchanged. Read it together with `type`: the vocabularies are per-resource, so a `deposit` never carries a transfer status and the reverse."
          },
          "amount": {
            "$ref": "#/components/schemas/DecimalAmount"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the movement was recorded (RFC 3339, UTC). This is the field the feed is ordered by."
          },
          "customer_id": {
            "type": "string",
            "description": "Present when the movement belongs to a customer of yours."
          },
          "network": {
            "type": "string",
            "description": "Present on `deposit`; the network the credit arrived on."
          }
        }
      },
      "Rail": {
        "type": "string",
        "description": "Canonical payment-rail vocabulary, shared by named accounts and payout\ndestinations. The table below answers one question: can you open a named\naccount on this rail (machine-readable in `x-ozav-rail-availability`)?\nPaying out is a different question, answered by the corridor table in\n`POST /payout-destinations`. The two differ: `fedwire`, for example, is\nlive for payouts, while opening a USD account on it is sandbox only.\n\n| Rail | Corridor | Availability |\n|---|---|---|\n| `pix` | BRL - Brazil instant payments | available |\n| `fedwire` | USD - US wire | sandbox only |\n| `sepa` | EUR - SEPA credit transfer | live (behind enablement) |\n| `spei` | MXN - Mexico SPEI | sandbox only |\n| `swift` | Cross-border SWIFT | live (behind enablement) |\n| `faster_payments` | GBP - UK Faster Payments | live (behind enablement) |\n\n**`ach` and `co_transfer` are not offered.** Requests for accounts, payout\ndestinations and quotes use `RequestRail`, which leaves both out, and a\n`fiat_payment` source on `ach` is refused with `422 RAIL_NOT_AVAILABLE`.\nThey stay in this enum only because older records carry them.\n\n**`live (behind enablement)`** means the corridor is served in production,\nbut not to everyone: opening an account still requires that OZAV has\nenabled the specific customer for foreign-currency named accounts. Until\nthen a `live` request is refused with a named error that says which step\nis missing - not with `ACCOUNT_RAIL_NOT_AVAILABLE`, which would send you\nlooking at the corridor instead of the customer.\n\n**`sandbox only` is a real, usable state, and not a promise of later.**\nThe corridor is fully exercisable with a `test` key: you can open the\naccount, read its receiving instructions and build against the exact\nresponse shape production will return. What it does not have is a\nsettlement partner behind it, so a `live` key is refused with\n`ACCOUNT_RAIL_NOT_AVAILABLE` rather than opening something that cannot\nreceive. Build against it in sandbox; the request you write does not\nchange when the corridor goes live.\n",
        "enum": [
          "pix",
          "ach",
          "fedwire",
          "sepa",
          "spei",
          "swift",
          "faster_payments",
          "co_transfer",
          "uaefts",
          "eft",
          "cnaps",
          "zengin",
          "nip",
          "cce",
          "rtc"
        ],
        "x-ozav-rail-availability": {
          "pix": "sandbox_active",
          "fedwire": "sandbox_only",
          "sepa": "live_gated",
          "spei": "sandbox_only",
          "swift": "live_gated",
          "faster_payments": "live_gated"
        }
      },
      "RequestRail": {
        "type": "string",
        "description": "The rails a request can name: every value of `Rail` except `ach` and\n`co_transfer`, which are not offered and appear only on older records.\n",
        "enum": [
          "pix",
          "fedwire",
          "sepa",
          "spei",
          "swift",
          "faster_payments",
          "uaefts",
          "eft",
          "cnaps",
          "zengin",
          "nip",
          "cce",
          "rtc"
        ]
      },
      "Network": {
        "type": "string",
        "description": "Public v2 blockchain network vocabulary for wallet balances and crypto\ntransfer legs. This enum is the wire contract today and\nlists only networks accepted by public v2 fields now. The custody stack\ncan have additional active networks before those networks are accepted\nby every public API flow; see `x-ozav-network-matrix` for that explicit\nsplit.\n",
        "enum": [
          "ethereum",
          "base",
          "optimism",
          "polygon",
          "avalanche_c_chain",
          "worldchain",
          "unichain",
          "linea",
          "sonic",
          "sei",
          "celo",
          "zksync_era",
          "solana",
          "tron",
          "stellar"
        ],
        "x-ozav-network-availability": {
          "ethereum": "available",
          "base": "deposit_paused",
          "optimism": "deposit_paused",
          "polygon": "opt_in",
          "avalanche_c_chain": "deposit_paused",
          "worldchain": "deposit_paused",
          "unichain": "deposit_paused",
          "linea": "deposit_paused",
          "sonic": "deposit_paused",
          "sei": "deposit_paused",
          "celo": "deposit_paused",
          "zksync_era": "deposit_paused",
          "solana": "available",
          "tron": "available",
          "stellar": "opt_in"
        },
        "x-ozav-network-matrix": {
          "ethereum": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC",
              "USDT"
            ],
            "verified_assets": [
              "USDC",
              "USDT"
            ],
            "native_cross_network": true
          },
          "base": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": true
          },
          "optimism": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": true
          },
          "polygon": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC",
              "USDT"
            ],
            "verified_assets": [
              "USDC",
              "USDT"
            ],
            "native_cross_network": true
          },
          "avalanche_c_chain": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC",
              "USDT"
            ],
            "verified_assets": [
              "USDC",
              "USDT"
            ],
            "native_cross_network": true
          },
          "worldchain": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": true
          },
          "unichain": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": false
          },
          "linea": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": false
          },
          "sonic": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": false
          },
          "sei": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": false
          },
          "celo": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": false
          },
          "zksync_era": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": false
          },
          "solana": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC",
              "USDT"
            ],
            "verified_assets": [
              "USDC",
              "USDT"
            ],
            "native_cross_network": true
          },
          "tron": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDT"
            ],
            "verified_assets": [
              "USDT"
            ],
            "native_cross_network": false
          },
          "stellar": {
            "public_v2": "accepted",
            "custody_stack": "active",
            "assets": [
              "USDC"
            ],
            "verified_assets": [
              "USDC"
            ],
            "native_cross_network": false
          },
          "tempo": {
            "public_v2": "not_accepted",
            "custody_stack": "active",
            "assets": [
              "USDT"
            ],
            "verified_assets": [
              "USDT"
            ],
            "native_cross_network": false
          },
          "arbitrum": {
            "public_v2": "not_accepted",
            "custody_stack": "active",
            "assets": [
              "USDC",
              "USDT"
            ],
            "verified_assets": [
              "USDC",
              "USDT"
            ],
            "native_cross_network": true
          },
          "aptos": {
            "public_v2": "not_accepted",
            "custody_stack": "deferred",
            "assets": [],
            "verified_assets": [],
            "native_cross_network": false
          },
          "sui": {
            "public_v2": "not_accepted",
            "custody_stack": "deferred",
            "assets": [],
            "verified_assets": [],
            "native_cross_network": false
          }
        }
      },
      "Metadata": {
        "type": "object",
        "description": "Free-form string map (max 50 keys).",
        "additionalProperties": {
          "type": "string"
        }
      },
      "MonetaryAmount": {
        "type": "object",
        "description": "An amount paired with its currency, at 6-fractional-digit accounting precision.",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/DecimalAmount"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          }
        }
      },
      "TransferMonetaryAmount": {
        "type": "object",
        "required": [
          "amount",
          "currency"
        ],
        "description": "Amount block accepted on transfer creation (8-fractional-digit precision).",
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/TransferDecimalAmount"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          }
        }
      },
      "Address": {
        "type": "object",
        "description": "A postal address. `line1`, `city`, and `country` are required; the rest are optional. Jurisdictions that structure the address (Brazil among them) need it broken out - provide the discrete `number`, `neighborhood`, and (where relevant) `complement` fields, with `line1` then holding the street name alone. Which of these are required to onboard depends on jurisdiction and is reported in `Customer.requirements.missing_fields` (a Brazilian customer needs `line1`, `number`, `neighborhood`, `city`, `state`, and `postal_code`).",
        "required": [
          "line1",
          "city",
          "country"
        ],
        "properties": {
          "line1": {
            "type": "string",
            "description": "Street. Where an address is not structured into discrete components, the building number may be included here; when the `number` field is provided, `line1` is the street name alone."
          },
          "line2": {
            "type": "string",
            "description": "Second address line - unit or suite. Optional. See also `complement`."
          },
          "number": {
            "type": "string",
            "description": "Building / house number, kept separate from the street. Optional in general; required for Brazilian onboarding."
          },
          "neighborhood": {
            "type": "string",
            "description": "Neighborhood or district (e.g. Brazilian `bairro`). Optional in general; required for Brazilian onboarding."
          },
          "complement": {
            "type": "string",
            "description": "Address complement - apartment, block, or reference (e.g. Brazilian `complemento`). Optional."
          },
          "city": {
            "type": "string",
            "description": "City or municipality."
          },
          "state": {
            "type": "string",
            "description": "State or province - an ISO 3166-2 subdivision code where applicable."
          },
          "postal_code": {
            "type": "string",
            "description": "Postal or ZIP code."
          },
          "country": {
            "type": "string",
            "description": "Country as an ISO 3166-1 alpha-2 code (e.g. BR, US).",
            "pattern": "^[A-Z]{2}$"
          }
        }
      },
      "Phone": {
        "type": "object",
        "description": "A contact phone split into an international dialing code and the national number, so onboarding requirements that need the phone's country (Brazil among them) can read it without parsing an E.164 string. Both parts are required when a phone is supplied.",
        "required": [
          "country_code",
          "number"
        ],
        "properties": {
          "country_code": {
            "type": "string",
            "description": "International dialing code, e.g. `+55` or `55`.",
            "pattern": "^\\+?\\d{1,4}$"
          },
          "number": {
            "type": "string",
            "maxLength": 32,
            "description": "National phone number. At most 20 digits."
          }
        }
      },
      "HolderTaxId": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "country",
          "type",
          "value"
        ],
        "description": "Tax / registration identifier of the **account holder**, who may be a person or a company.\n\nThe `type` enum is the union of the personal (`cpf`, `ssn`, `nif`, `passport`) and company\n(`cnpj`, `ein`, `company_number`) identifiers, because a payout destination can be held by\neither. Use `other` only when none applies - it is the one value that carries no meaning and\ngets no validation.\n\n`cpf` and `cnpj` are checked against their own check digits and rejected with `400` when the\nnumber does not add up.\n",
        "properties": {
          "country": {
            "type": "string",
            "description": "Issuing country as an ISO 3166-1 alpha-2 code.",
            "pattern": "^[A-Z]{2}$"
          },
          "type": {
            "type": "string",
            "description": "The kind of identifier carried in `value`:\n- `cpf` - Brazilian individual taxpayer registry. Check digits are verified.\n- `cnpj` - Brazilian company registry. Check digits are verified.\n- `ssn` - US Social Security Number.\n- `ein` - US Employer Identification Number.\n- `nif` - Iberian/European tax identification number.\n- `company_number` - company registry number outside Brazil/US.\n- `passport` - passport number.\n- `other` - any identifier not covered above. Carries no meaning and gets no validation.\n",
            "enum": [
              "cpf",
              "cnpj",
              "ssn",
              "ein",
              "nif",
              "company_number",
              "passport",
              "other"
            ]
          },
          "value": {
            "type": "string",
            "description": "The identifier itself. Punctuation is accepted and ignored."
          }
        }
      },
      "TaxId": {
        "type": "object",
        "description": "A national tax or personal identifier for an individual.",
        "required": [
          "country",
          "type",
          "value"
        ],
        "properties": {
          "country": {
            "type": "string",
            "description": "Issuing country as an ISO 3166-1 alpha-2 code.",
            "pattern": "^[A-Z]{2}$"
          },
          "type": {
            "type": "string",
            "description": "The kind of identifier carried in `value`:\n- `cpf` - Brazilian individual taxpayer registry (Cadastro de Pessoas Físicas).\n- `ssn` - US Social Security Number.\n- `nif` - Iberian/European tax identification number (Número de Identificação Fiscal).\n- `passport` - passport number.\n- `other` - any identifier not covered above.\n",
            "enum": [
              "cpf",
              "ssn",
              "nif",
              "passport",
              "other"
            ]
          },
          "value": {
            "type": "string",
            "description": "The identifier itself, as issued. Formatting characters are not required."
          }
        }
      },
      "Registration": {
        "type": "object",
        "description": "A company registration or incorporation identifier for a business.",
        "required": [
          "country",
          "type",
          "value"
        ],
        "properties": {
          "country": {
            "type": "string",
            "description": "Country of registration as an ISO 3166-1 alpha-2 code.",
            "pattern": "^[A-Z]{2}$"
          },
          "type": {
            "type": "string",
            "description": "The kind of registration identifier carried in `value`:\n- `cnpj` - Brazilian company registry (Cadastro Nacional da Pessoa Jurídica).\n- `ein` - US Employer Identification Number.\n- `nif` - Iberian tax/registry number (Número de Identificação Fiscal).\n- `company_number` - a national company/registry number (e.g. Companies House).\n- `other` - any registration identifier not covered above.\n",
            "enum": [
              "cnpj",
              "ein",
              "nif",
              "company_number",
              "other"
            ]
          },
          "value": {
            "type": "string",
            "description": "The registration number itself, as issued."
          }
        }
      },
      "CapabilityKind": {
        "type": "string",
        "description": "Canonical OZAV verification / screening capability taxonomy - the checks\nyou can request for a customer (`POST /verifications`) and the keys of a\ncustomer's `verification.checks` map.\n\nIdentity / KYC (individuals):\n- `kyc_document` - government ID document capture and authentication.\n- `liveness` - confirms the person is physically present (anti-spoofing).\n- `face_match` - the selfie matches the ID document portrait.\n- `proof_of_address` - a document evidencing residential address.\n- `age_estimation` - estimate that the person is above an age threshold.\n\nBusiness / KYB (companies):\n- `kyb` - business registration and standing verification.\n- `ubo` - ultimate-beneficial-owner identification and checks.\n\nAML (name-based):\n- `aml_name_screening` - point-in-time designated-list name screening.\n- `aml_ongoing_monitoring` - continuous re-screening for new matches.\n\nTransaction / on-chain:\n- `wallet_screening` - blockchain-address risk screening.\n- `travel_rule` - originator/beneficiary information exchange for crypto sends.\n\nContactability:\n- `email_verify` - the email address is reachable and controlled by the customer.\n- `phone_verify` - the phone number is reachable and controlled by the customer.\n\nFinancial:\n- `economic_capacity` - assessment of the customer's economic capacity (declared/evidenced financial standing) used to justify transaction limits.\n",
        "enum": [
          "kyc_document",
          "liveness",
          "face_match",
          "proof_of_address",
          "age_estimation",
          "kyb",
          "ubo",
          "aml_name_screening",
          "aml_ongoing_monitoring",
          "wallet_screening",
          "travel_rule",
          "email_verify",
          "phone_verify",
          "economic_capacity"
        ]
      },
      "CheckOutcome": {
        "type": "string",
        "description": "Outcome of a single verification check.\n- `not_started` - the check has not begun.\n- `pending` - the check is in progress; no outcome yet.\n- `pass` - the check succeeded.\n- `fail` - the check did not succeed.\n- `review` - inconclusive; queued for manual review (never silently passed).\n",
        "enum": [
          "pass",
          "fail",
          "review",
          "pending",
          "not_started"
        ]
      },
      "CheckResult": {
        "type": "object",
        "description": "The result of a single verification check.",
        "properties": {
          "outcome": {
            "$ref": "#/components/schemas/CheckOutcome"
          },
          "verified_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the check reached its outcome (RFC 3339 / ISO 8601, UTC). Present once the check has completed."
          }
        }
      },
      "CustomerStatus": {
        "type": "string",
        "description": "Onboarding / KYC lifecycle state of a customer.\n\n| Value | Meaning |\n|---|---|\n| `pending` | Created; verification has not completed. The customer cannot transact yet. |\n| `in_review` | Checks were submitted and are under (automated or manual) review. |\n| `approved` | Verification passed. The customer may transact once OZAV has everything a payout will need; see the note below. |\n| `rejected` | Verification failed; the customer cannot be onboarded. |\n| `suspended` | A previously active customer has been blocked by a compliance or operational action. |\n\n**`approved` is about the checks, not about being able to operate.**\nOZAV activates a customer only when it also holds everything a payout\nwill need: the holder's name, a valid tax document for the country\ndeclared on it, and an address. A Brazilian company with a valid CNPJ\nis exempt from the address, because the public registry supplies it.\n\nA customer can therefore sit at `approved` and still be refused, with\n`422` on `POST /accounts` and on the money paths. The gap shows up there,\nbefore any funds are committed, and not at payout time.\n\nThe request body cannot resolve it. Send the missing field through the\ncustomer update path, or ask OZAV: the blocked queue names each\ncustomer and the exact field it is waiting on.\n",
        "enum": [
          "pending",
          "in_review",
          "approved",
          "rejected",
          "suspended"
        ]
      },
      "CustomerIndividual": {
        "type": "object",
        "description": "Identity attributes for an `individual` customer or associated person. Every field except `legal_name` is optional at creation so multi-jurisdiction and incremental onboarding are supported - but onboarding a customer to completion needs more. What is still owed is reported per jurisdiction in `Customer.requirements.missing_fields`; for a Brazilian individual that is `date_of_birth`, `phone`, `email`, a full structured `residential_address`, and `tax_id`.",
        "required": [
          "legal_name"
        ],
        "properties": {
          "legal_name": {
            "type": "string",
            "description": "Full legal name as it appears on the identity document."
          },
          "mother_name": {
            "type": "string",
            "description": "Mother's full name. Accepted and stored; not required for onboarding."
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "description": "Date of birth (ISO 8601 date, YYYY-MM-DD). Validated as a real calendar date, not in the future, at least 18 years ago and not more than 120 - a structurally valid string that is none of those is rejected at create, not accepted and passed on."
          },
          "nationality": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "Nationality as an ISO 3166-1 alpha-2 country code. It feeds risk assessment, and it does not change which fields are required: the required-field set is driven by the country on `tax_id` (for an individual) or `registration` (for a business), not by nationality and not by the currency of any account you later open. It does not change which other fields are required (the tax-id country drives that branch), but `nationality` is itself reported in `requirements.missing_fields` until you supply it."
          },
          "phone": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Phone"
              }
            ],
            "description": "Structured contact phone (dialing code + number), distinct from the loose top-level `Customer.phone` string. Required for Brazilian onboarding, which needs the phone's country explicitly."
          },
          "is_pep": {
            "type": "boolean",
            "description": "Politically-exposed-person self-declaration. Optional. When omitted, OZAV's own screening records the answer: not a PEP when its recent screening read the PEP lists without a match. `true`, or a PEP match, goes to OZAV compliance review."
          },
          "gender": {
            "type": "string",
            "enum": [
              "male",
              "female",
              "other"
            ],
            "description": "Gender as the person declares it. Optional; never defaulted. Required on the authorized person of a business before USD, EUR or GBP accounts can open. A `PATCH` that leaves it out keeps the value already given."
          },
          "tax_id": {
            "$ref": "#/components/schemas/TaxId"
          },
          "residential_address": {
            "$ref": "#/components/schemas/Address"
          },
          "occupation": {
            "type": "string",
            "description": "The person's profession or occupation."
          },
          "source_of_funds": {
            "type": "string",
            "description": "Declared origin of the funds the person will move (e.g. salary, savings, sale of property). Reported in `requirements.missing_fields` until supplied."
          },
          "monthly_income_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Self-declared monthly income in USD (amounts are USD-denominated). This is the capacity the expected monthly volume is measured against: without it the economic-capacity assessment has nothing to compare and returns no verdict. `0` is a valid answer (declared no income) and is not treated as absent. Reported in `requirements.missing_fields` until supplied."
          },
          "expected_monthly_volume_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Expected monthly transaction movement in USD. This is the amount the economic-capacity assessment measures; what it measures against is the declared capacity (`monthly_income_usd` for an individual, `annual_revenue_usd` for a business). Supplying this alone yields no assessment. Reported in `requirements.missing_fields` until supplied."
          },
          "account_purpose": {
            "type": "string",
            "description": "Intended use of the account / purpose of the relationship."
          }
        }
      },
      "AssociatedPersonIndividualUpdate": {
        "type": "object",
        "description": "The `individual` block of an associated-person update. Same fields as `CustomerIndividual`, all optional: only the fields you include change, and omitted fields keep their stored value. To declare a gender, send `{ \"gender\": \"female\" }` alone.",
        "properties": {
          "legal_name": {
            "type": "string",
            "description": "Full legal name as it appears on the identity document."
          },
          "mother_name": {
            "type": "string",
            "description": "Mother's full name. Accepted and stored; not required for onboarding."
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "description": "Date of birth (ISO 8601 date, YYYY-MM-DD). Validated as a real calendar date, not in the future, at least 18 years ago and not more than 120 - a structurally valid string that is none of those is rejected at create, not accepted and passed on."
          },
          "nationality": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "Nationality as an ISO 3166-1 alpha-2 country code. It feeds risk assessment, and it does not change which fields are required: the required-field set is driven by the country on `tax_id` (for an individual) or `registration` (for a business), not by nationality and not by the currency of any account you later open. It does not change which other fields are required (the tax-id country drives that branch), but `nationality` is itself reported in `requirements.missing_fields` until you supply it."
          },
          "phone": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Phone"
              }
            ],
            "description": "Structured contact phone (dialing code + number), distinct from the loose top-level `Customer.phone` string. Required for Brazilian onboarding, which needs the phone's country explicitly."
          },
          "is_pep": {
            "type": "boolean",
            "description": "Politically-exposed-person self-declaration. Optional at creation; treated as `false` when omitted."
          },
          "gender": {
            "type": "string",
            "enum": [
              "male",
              "female",
              "other"
            ],
            "description": "Gender as the person declares it. Optional; never defaulted. Required on the authorized person of a business before USD, EUR or GBP accounts can open. A `PATCH` that leaves it out keeps the value already given."
          },
          "tax_id": {
            "$ref": "#/components/schemas/TaxId"
          },
          "residential_address": {
            "$ref": "#/components/schemas/Address"
          },
          "occupation": {
            "type": "string",
            "description": "The person's profession or occupation."
          },
          "source_of_funds": {
            "type": "string",
            "description": "Declared origin of the funds the person will move (e.g. salary, savings, sale of property). Reported in `requirements.missing_fields` until supplied."
          },
          "monthly_income_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Self-declared monthly income in USD (amounts are USD-denominated). This is the capacity the expected monthly volume is measured against: without it the economic-capacity assessment has nothing to compare and returns no verdict. `0` is a valid answer (declared no income) and is not treated as absent. Reported in `requirements.missing_fields` until supplied."
          },
          "expected_monthly_volume_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Expected monthly transaction movement in USD. This is the amount the economic-capacity assessment measures; what it measures against is the declared capacity (`monthly_income_usd` for an individual, `annual_revenue_usd` for a business). Supplying this alone yields no assessment. Reported in `requirements.missing_fields` until supplied."
          },
          "account_purpose": {
            "type": "string",
            "description": "Intended use of the account / purpose of the relationship."
          }
        }
      },
      "CustomerBusiness": {
        "type": "object",
        "description": "Identity attributes for a `business` customer. Only `legal_name` and `registration` are required at creation; onboarding a business to completion needs more, reported per jurisdiction in `Customer.requirements.missing_fields`. For a Brazilian business that is `phone`, a business email (the customer `email` serves as the business email), a full structured `registered_address`, `company_type`, and `owners` - at least one associated person owning ≥25%, added via `POST /customers/{customer_id}/associated_persons`.",
        "required": [
          "legal_name",
          "registration"
        ],
        "properties": {
          "legal_name": {
            "type": "string",
            "description": "Registered legal name of the company."
          },
          "trade_name": {
            "type": "string",
            "description": "Trading / doing-business-as name, if different from the legal name."
          },
          "registration": {
            "$ref": "#/components/schemas/Registration"
          },
          "incorporation_date": {
            "type": "string",
            "format": "date",
            "description": "Date the company was incorporated (ISO 8601 date)."
          },
          "legal_structure": {
            "type": "string",
            "description": "Legal form of the entity (e.g. Ltda, LLC, S.A.). Accepted and stored on the customer record, and it is not requested in `requirements.missing_fields`: no automated assessment reads it today, so it is offered for completeness rather than asked for. For Brazil the field an onboarding actually reads is `company_type`."
          },
          "company_type": {
            "type": "string",
            "description": "Company classification. For Brazil the onboarding values are `MEI` (microempreendedor individual) and `PJ` (pessoa jurídica). Required for Brazilian onboarding; optional at creation."
          },
          "industry": {
            "type": "string",
            "description": "Primary industry or line of business."
          },
          "cnae_codes": {
            "type": "array",
            "maxItems": 20,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "pattern": "^([0-9]{7}|[0-9]{4}-[0-9]/[0-9]{2})$",
              "example": "6201-5/01"
            },
            "description": "Brazilian CNAE activity codes the company declares, either as 7 digits (`6201501`) or in the notation of the CNPJ card (`6201-5/01`). They are stored as digits only; `6201-5/01` and `6201501` are the same code, and the stored list never repeats one (`uniqueItems` describes the stored list: a request that repeats a code is accepted and stored once, and the 20-code limit counts codes after repeats are removed). The first one is the primary activity. Send an empty array to clear the list. Used only when the public registry (Receita Federal) does not return an activity code for the company's CNPJ; the registry always prevails, and a declared code is recorded as declared by the customer, never as official."
          },
          "website": {
            "type": "string",
            "format": "uri",
            "description": "Company website URL."
          },
          "phone": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Phone"
              }
            ],
            "description": "Structured contact phone for the company (dialing code + number). Required for Brazilian onboarding."
          },
          "registered_address": {
            "$ref": "#/components/schemas/Address"
          },
          "source_of_funds": {
            "type": "string",
            "description": "Declared source of the funds the business will move. Reported in `requirements.missing_fields` until supplied."
          },
          "expected_monthly_volume_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Expected monthly transaction movement in USD. This is the amount the economic-capacity assessment measures; what it measures against is the declared capacity (`monthly_income_usd` for an individual, `annual_revenue_usd` for a business). Supplying this alone yields no assessment. Reported in `requirements.missing_fields` until supplied."
          },
          "annual_revenue_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Declared annual revenue in USD (amounts are USD-denominated). This is the capacity the expected monthly volume is measured against (the assessment reads it as `annual_revenue_usd / 12`): without it the economic-capacity assessment has nothing to compare and returns no verdict. `0` is a valid answer and is not treated as absent. Reported in `requirements.missing_fields` until supplied."
          },
          "expected_daily_volume_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Expected daily transaction movement in USD. A separate declaration, not the monthly figure divided by anything: account opening asks for both, and a derived number would be a value the customer never declared. Reported in `requirements.missing_fields` until supplied."
          },
          "is_regulated_activity": {
            "type": "boolean",
            "description": "Whether the business performs a regulated activity. A declared value (including `false`) counts as answered; absent is reported in `requirements.missing_fields`. When `true`, supply `regulator` and `regulated_activity_details`."
          },
          "regulator": {
            "type": "string",
            "description": "Name of the supervising regulator when the activity is regulated."
          },
          "regulated_activity_details": {
            "type": "string",
            "description": "Detail of the regulated activity (licence, scope) when regulated."
          },
          "nature_of_business": {
            "type": "string",
            "description": "Description of what the business does / the services it provides."
          },
          "aml_program": {
            "type": "string",
            "description": "Description of the business's compliance / AML program. Accepted and stored on the customer record, and it is not requested in `requirements.missing_fields`: no automated assessment reads it today, so it is offered for completeness rather than asked for. "
          },
          "operating_jurisdictions": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            },
            "description": "Countries the business operates in (ISO 3166-1 alpha-2 codes). Accepted and stored on the customer record, and it is not requested in `requirements.missing_fields`: no automated assessment reads it today, so it is offered for completeness rather than asked for. Sanctions screening does not currently read this list."
          },
          "account_purpose": {
            "type": "string",
            "description": "Intended use of the account / purpose of the relationship."
          },
          "bc_protege_disabled": {
            "type": "boolean",
            "description": "Brazilian business only: the company declares it has turned off BC Protege+ (the Central Bank of Brazil service that blocks institutions from opening accounts in the company's name; see https://www.gov.br/pt-br/servicos/proteger-se-com-o-bc-protege), or never turned it on. Required before a BRL account is requested for a business: without `true`, `POST /accounts` refuses with `422 ACCOUNT_PROFILE_INCOMPLETE` and `missing` lists `business.bc_protege_disabled` (sandbox too). Only the company can turn the service off, with its own gov.br login; send `true` once it has. Absent = not declared."
          }
        }
      },
      "CustomerVerification": {
        "type": "object",
        "description": "The customer's verification posture - the mode applied, the overall status, and per-check results.",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "full",
              "reliance"
            ],
            "description": "How this customer is verified:\n- `full` - OZAV performs the verification checks directly.\n- `reliance` - you attest, under a reliance arrangement, that verification\n  was performed on your side; available only to organizations holding the\n  `kyc_reliance` entitlement (contractual attestation). Reliance mode does\n  not auto-approve a customer in the public API response; the customer's\n  `status` reflects the current onboarding state.\n\nDesignated-list screening is always performed by OZAV regardless of mode.\n\n**When `mode` is absent.** This field is optional, and its absence is meaningful:\nOZAV cannot state a verification posture for this record. Treat it as\n\"posture unknown\". It is not equivalent to `full`, and must never be read as one.\n\nTwo different internal situations produce it, and the API does not distinguish them:\neither no verification posture was ever recorded (typical for records created outside\nthe API v2 onboarding path, which carry no canonical verification block), or a posture\nexists internally but is not expressible in this API version. Both mean the same thing\nto you: do not treat this record as verified on the strength of this object.\n\n**Precedence against `status`.** `status` is required and is always present. When\n`mode` is absent, `status` describes the internal onboarding state only and is not a\nverification attestation - a record can read `status: approved` with no `mode`, and\nthat combination must not be used as evidence that verification was performed.\n\n**`requirements.missing_fields` is a lower bound here.** It is still emitted and still\nuseful, but when the canonical block is missing OZAV cannot resolve the record's\njurisdiction, so jurisdiction-specific requirements are not listed. Satisfying every\nfield it names may still leave the record incomplete.\n\nThe record is still returned in full, and other records are unaffected: one\nincomplete record never fails a call.\n"
          },
          "status": {
            "$ref": "#/components/schemas/CustomerStatus"
          },
          "checks": {
            "type": "object",
            "description": "Per-capability check results, keyed by `CapabilityKind`. Empty until checks have run.",
            "additionalProperties": {
              "$ref": "#/components/schemas/CheckResult"
            }
          }
        }
      },
      "CustomerDecision": {
        "type": "object",
        "description": "OZAV's compliance decision on this customer, as it stands - the answer to\n\"was it approved, and if not, why, and whose move is it\".\n\n`status` is finer than the customer's `status`: a customer reads `pending`\nwhile OZAV is reviewing and while OZAV is waiting on documents from you,\nand only `decision.status` tells those apart (`pending_review` vs\n`information_requested`). Read `requirements.owner` for whose move it is.\n\n`reason_code` is a closed vocabulary (below) and `reason` is its English\nlabel. Free-text analyst notes never travel: a rejection recorded before\nthis vocabulary existed reads `policy`.\n\nPresent on `GET /customers/{customer_id}`, on every record of\n`GET /customers`, and in the `customer.updated` webhook body - which is\nemitted on approval, on rejection, and when information is requested.\n",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pending_review",
              "information_requested",
              "approved",
              "rejected",
              "expired"
            ],
            "description": "- `pending_review` - OZAV is reviewing; nothing is owed by you right now.\n- `information_requested` - OZAV asked for documents or corrections;\n  `requirements.documents_due` lists them with the deadline.\n- `approved` / `rejected` - final.\n- `expired` - nothing was sent for this customer for 30 days while it\n  was your move. Not final and not a rejection: it reopens on its own\n  as soon as you send anything (a document, a profile update, an\n  identity session), and `requirements.owner` stays `integrator`.\n  Treat any status you don't recognise as not final.\n"
          },
          "reason_code": {
            "$ref": "#/components/schemas/DecisionReasonCode"
          },
          "reason": {
            "type": "string",
            "description": "English label of `reason_code`. Present only with `reason_code`."
          },
          "decided_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the current decision was recorded. Absent while `pending_review`."
          }
        }
      },
      "CustomerSla": {
        "type": "object",
        "description": "OZAV's response-time commitment on this customer, as a clock: whose move\nit is, until when, and whether the deadline has passed.\n\n- OZAV decides within **48 business hours** (Mon-Fri, Brazilian national\n  holidays excluded) once the record is complete - profile fields filled\n  and the required documents accepted. While that clock runs `owner` is\n  `ozav`.\n- When OZAV asks you for a document or a correction the clock is yours:\n  **30 days** for a document, **15 days** for a correction (`owner`\n  `integrator`; `due_at` mirrors `requirements.documents_due[].due_at`).\n- `owner` is `none` when the decision is final, when no onboarding case is\n  open, or while OZAV is waiting on a third party (that wait counts\n  against no one).\n\nPresent on `GET /customers/{customer_id}`, on every record of\n`GET /customers`, and in the `customer.updated` webhook body.\n",
        "required": [
          "owner",
          "due_at",
          "breached"
        ],
        "properties": {
          "owner": {
            "type": "string",
            "enum": [
              "ozav",
              "integrator",
              "none"
            ],
            "description": "Whose clock is running."
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the running clock expires. `null` when `owner` is `none`."
          },
          "breached": {
            "type": "boolean",
            "description": "`true` when `due_at` is in the past and the move is still owed. Always `false` when `owner` is `none`."
          }
        }
      },
      "DecisionReasonCode": {
        "type": "string",
        "description": "Why a customer was rejected, or why information was requested. Closed\nvocabulary; each code has a fixed English label in `decision.reason`.\n\nRejection codes: `sanctions_match`, `pep_unmitigated`, `identity_failed`,\n`document_invalid`, `capacity_unproven`, `inconsistent_data`,\n`restricted_jurisdiction`, `no_response`, `fraud_suspected`, `policy`.\n\nRequest codes (also on `requirements.documents_due[].reason_code` and on\nthe `requirement.due` webhook `reason`): `document_missing`,\n`document_invalid`, `data_inconsistent`, `capacity_unproven`,\n`identity_session_needed`, `half_of_limit_reached`, `registration_incomplete`,\n`destination_unidentified`.\n\n`destination_unidentified` asks who holds a wallet the customer sent crypto to:\nthe customer's own self-hosted wallet, or the exchange where the customer has an\naccount. Answer it with `POST /customers/{customer_id}/destination-identifications`.\n",
        "enum": [
          "sanctions_match",
          "pep_unmitigated",
          "identity_failed",
          "document_invalid",
          "capacity_unproven",
          "inconsistent_data",
          "restricted_jurisdiction",
          "no_response",
          "fraud_suspected",
          "policy",
          "document_missing",
          "data_inconsistent",
          "identity_session_needed",
          "half_of_limit_reached",
          "registration_incomplete",
          "destination_unidentified"
        ]
      },
      "DocumentDue": {
        "type": "object",
        "description": "A document OZAV's compliance asked for on this customer, with the deadline.",
        "required": [
          "key",
          "label",
          "status"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "The requirement key (e.g. `proof_of_address`, `bank_statement`, `government_id_front`; and `tax_registration` and `pep_declaration`, the two a Brazilian payment institution requires for a BRL account)."
          },
          "doc_type": {
            "$ref": "#/components/schemas/CustomerDocumentType",
            "description": "The `doc_type` to send in `POST /customers/{customer_id}/documents` to satisfy this item - the same vocabulary as `still_required` on the document list. Absent when the requirement is not an API document."
          },
          "label": {
            "type": "string",
            "description": "Human label of the requirement."
          },
          "status": {
            "type": "string",
            "enum": [
              "due",
              "submitted",
              "accepted",
              "rejected"
            ],
            "description": "`due` - owed by you, the clock runs. `submitted` - a file landed and stopped\nthe clock; OZAV still has to accept it. `accepted` - done. `rejected` - the\nfile was refused; a new `due` line follows with the reason.\n"
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Deadline for `due` items; `null` when none applies."
          },
          "reason_code": {
            "$ref": "#/components/schemas/DecisionReasonCode"
          },
          "purpose": {
            "type": "string",
            "enum": [
              "limit_increase",
              "account_opening",
              "registration_data",
              "travel_rule"
            ],
            "description": "`limit_increase` - this item does not hold the approval back. It is what OZAV\nneeds to raise the customer's limit above the entry level (economic capacity\nnot yet proven: bank statements, financial statements, tax return).\n`account_opening` - this item does not hold the approval back either. It is\nneeded to open the customer's named account.\n`registration_data` - a registration field (declared income or revenue,\nexpected volume) that completes the profile. It does not hold the approval\nback: until it arrives the risk score uses a conservative provisional value.\n`travel_rule` - who holds a destination wallet the customer sent crypto to\n(see `destination`). It does not hold the approval back and blocks no send.\nAnswer it with `POST /customers/{customer_id}/destination-identifications`.\nA `due` line with any of these purposes does not turn `owner` to `integrator` nor\n`decision.status` to `information_requested`. Absent - the item is needed for\napproval.\n"
          },
          "destination": {
            "type": "object",
            "description": "Only on a `travel_rule` item - the wallet the item asks about.",
            "required": [
              "chain",
              "address"
            ],
            "properties": {
              "chain": {
                "type": "string",
                "description": "The network, lowercase (e.g. `tron`, `ethereum`)."
              },
              "address": {
                "type": "string",
                "description": "The destination address."
              }
            }
          }
        }
      },
      "Requirements": {
        "type": "object",
        "description": "Outstanding items blocking the customer from advancing through verification / onboarding - the honest \"here's what's still needed\". Present on the create response, on `GET /customers/{customer_id}`, and on every record in `GET /customers`. The list case matters: `CustomerVerification.mode` points here as the signal to read when a verification posture is absent, and that guidance would be unbacked if the field were not guaranteed on the list.",
        "properties": {
          "owner": {
            "type": "string",
            "enum": [
              "integrator",
              "ozav",
              "none"
            ],
            "description": "Whose move it is. `integrator` - OZAV is waiting on you: documents due,\ninformation requested, an approved customer with a non-empty `due`\n(see the next field), or a `pending_review` customer whose analysis\ncannot start yet because the record is incomplete (`missing_fields`)\nor the hosted verification session of a `full` customer has not\ncompleted. A missing `identity_document` - the individual's own, or\nan associated person's - also puts `owner` at `integrator`, under\n`reliance` too. `ozav` - OZAV is reviewing; nothing to do. `none` - the\ndecision is final and nothing is owed: a rejection, or an approval\nwhose `due` is empty.\n\nRead `documents_due`, `due`, `missing_fields` and `decision.status`\ntogether for what is actually owed. In a list read, `owners` and\n`representative` are reported in `missing_fields` for every business\n(associated persons are not counted there) and do not flip `owner` on\ntheir own; read the single customer for those two.\n\nAn approved customer with a non-empty `due` returns `integrator`, not\n`none`: there is still something for you to send. A rejected customer keeps\n`none` even with a non-empty `due`: the list survives the verdict, and\nno field reverses a rejection.\n"
          },
          "documents_due": {
            "type": "array",
            "description": "Every document OZAV's compliance has asked for on this customer - the ask,\nits current `status` and its deadline - and why (`reason_code`).\n\nIt does not shrink. A line stays after the file lands (`submitted`), after it\nis accepted (`accepted`) and after it is refused (`rejected`): this is the\nhistory of the asks, with where each one stands. To know whether anything is\nowed, read `status` per line, or read `owner` - `owner: none` means nobody\nowes anything right now.\n\nThe `requirement.due` webhook announces a new ask; this field is the pull side\nof the same fact.\n",
            "items": {
              "$ref": "#/components/schemas/DocumentDue"
            }
          },
          "associated_persons_due": {
            "type": "array",
            "description": "Which associated persons are holding the business back, and which field is\nmissing on each. Present only when there is at least one; absent means either\nnothing is missing or the people could not be read, and those are already\ncovered elsewhere (`missing_fields` carries `owners` when none is registered).\nAn empty array would claim \"we checked and all of them are complete\", which is\nmore than is known when the read did not happen.\n\nOZAV screens every person who owns 25% or more, carries the role `ubo`, or signs\nfor the company, and a screening needs a name, a tax id and a date of birth. While\nany of those is missing on any such person the business stays in review. The field names match what you send to\n`POST /customers/{customer_id}/associated-persons`, so the fix is a direct write.\n",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "The `associated_person_id` to correct."
                },
                "missing_fields": {
                  "type": "array",
                  "description": "Field names still needed on that person.",
                  "items": {
                    "type": "string",
                    "enum": [
                      "legal_name",
                      "tax_id",
                      "date_of_birth"
                    ]
                  }
                }
              }
            }
          },
          "due": {
            "type": "array",
            "description": "What the customer still owes before the account can operate - which is a different question from whether the verification passed. Empty is the normal state; a non-empty `due` on an `approved` customer means the verification is done and the account is still blocked, and `owner` reads `integrator`.\nThe set below is closed and comes from the check that guards withdrawals. The strings are human-readable Portuguese today, not machine tokens, so match them exactly rather than parsing them: `nome do titular` (holder name), `CPF valido` (a valid Brazilian individual tax id), `CNPJ valido` (a valid Brazilian company registration), `documento` (a tax id of any kind, for a non-Brazilian holder), `endereco (logradouro e cidade)` (street and city). A Brazilian company with a valid CNPJ is exempt from the address - the public registry already carries it.\nOne more entry exists only on the organization's own account (the customer that represents your organization itself): `org_self_case_review`. It means OZAV compliance has not yet finished reviewing that account; there is nothing for you to send, and `owner` reads `ozav` while it is the only entry.\nCapability kinds and document requests can also appear here from the onboarding path; `documents_due` is the structured view of those, with deadline and reason.\nTo clear an entry, send the corresponding field with `PATCH /customers/{customer_id}`. The check re-runs on that call: when it passes, the account activates and this array comes back empty in the same response. It is never removed, only emptied - an absent `due` would be indistinguishable from one nobody computed.",
            "items": {
              "type": "string"
            }
          },
          "due_codes": {
            "type": "array",
            "description": "The same thing as `due`, in stable machine tokens - added because `due` carries human-readable Portuguese and some integrators compare it by equality. `due` is unchanged and stays; read whichever fits your code.\nThe vocabulary is the same as `missing_fields`: `legal_name`, `tax_id`, `registration`, `residential_address`, `registered_address`. Two names for the same thing inside one object would be worse than prose. The token depends on the customer type - for a business, a missing document is `registration` and a missing address is `registered_address`.\n`org_self_case_review` (see `due`) appears here unchanged - it is already a token.\nOnly entries that come from the account check get a code. A document request also lands in `due`, and its structured home is `documents_due` (with deadline and reason) - inventing a token for it here would promise vocabulary nothing emits. So `due_codes` can be shorter than `due`; it is not positional.",
            "items": {
              "type": "string"
            }
          },
          "missing_fields": {
            "type": "array",
            "description": "Identity/onboarding fields still required for this customer, computed from its own data and jurisdiction-aware from the tax-id / registration country. Each token is the path of the request field you must supply, minus the `individual.` / `business.` prefix - so `date_of_birth` means `individual.date_of_birth`, and `residential_address` means `individual.residential_address`. Match them exactly; the set below is closed and generated from the code that emits it.\nFor a Brazilian individual: `date_of_birth`, `phone`, `email`, `residential_address` (a full structured address), and `tax_id`. `mother_name` is not in this set - it is still accepted and stored, it just does not hold a customer at `pending`. For a Brazilian business: `phone`, `email`, `registered_address`, `company_type`, and `owners` (at least one associated person owning ≥25%). Outside Brazil a base set applies (`legal_name`, `tax_id` / `registration`, `email`, and the address of the matching block).\nIndependently of jurisdiction, the risk profile is also reported here until answered: `source_of_funds` and `expected_monthly_volume_usd` for both customer types, plus `is_regulated_activity` and `expected_daily_volume_usd` for a business. A declared `false` counts as answered and is not reported.\nThe economic-capacity declaration is reported alongside the expected volume, and the pair is what makes the assessment possible: `monthly_income_usd` for an individual and `annual_revenue_usd` for a business. The expected volume is the amount being measured; the income or revenue is what it is measured against. Supplying only the volume produces no verdict. `0` is a valid answer for either and is not reported as still owed.\nAn individual also owes `occupation`, `nationality` and `account_purpose`. `is_pep` is not reported: the politically-exposed-person declaration is optional, and without it OZAV's own screening records the answer.\n`regulator` and `regulated_activity_details` are conditional: they are reported only while `is_regulated_activity` is `true`. A `false` answer does not ask for them, and an absent `is_regulated_activity` asks for that question itself rather than for its follow-up.\n`expected_daily_volume_usd` is a separate declaration, not the monthly figure divided by anything: account provisioning asks for both, and a derived number would be a value the customer never declared.\nA business also owes `nature_of_business`, `website`, `account_purpose` and `incorporation_date`. They stay optional on `POST /customers` - the customer is created and completed over time - but account opening asks for them later, so they are reported here from the start rather than surfacing as a refusal once your own customer is already waiting.\nAn empty array means nothing is outstanding for the tokens that can be cleared. `owners` is currently not one of them: it is reported for every business regardless of the associated persons on file, because neither read path supplies the qualifying-owner count. An empty array is therefore unreachable for a business, and gating your flow on it will block every business customer permanently. Read the tokens you care about instead of testing the array for emptiness.",
            "items": {
              "type": "string",
              "enum": [
                "expected_daily_volume_usd",
                "nature_of_business",
                "website",
                "account_purpose",
                "incorporation_date",
                "date_of_birth",
                "phone",
                "email",
                "residential_address",
                "registered_address",
                "tax_id",
                "registration",
                "legal_name",
                "company_type",
                "owners",
                "representative",
                "source_of_funds",
                "expected_monthly_volume_usd",
                "is_regulated_activity",
                "monthly_income_usd",
                "annual_revenue_usd",
                "occupation",
                "nationality",
                "regulator",
                "regulated_activity_details"
              ]
            }
          }
        }
      },
      "CustomerCreateRequest": {
        "type": "object",
        "required": [
          "type",
          "email"
        ],
        "description": "`email` is required. An `individual` customer additionally requires `individual.tax_id`;\na `business` customer requires `business.registration` (enforced server-side).\n",
        "properties": {
          "type": {
            "type": "string",
            "description": "Whether the customer is a natural person or a company:\n- `individual` - a person; supply the `individual` block (including `individual.tax_id`).\n- `business` - a company; supply the `business` block (including `business.registration`).\n",
            "enum": [
              "individual",
              "business"
            ]
          },
          "external_ref": {
            "type": "string",
            "description": "Your own identifier for this customer, echoed back on the resource. Optional; need not be unique."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Contact email for the customer. Required. At most 254 characters."
          },
          "phone": {
            "type": "string",
            "maxLength": 32,
            "description": "Contact phone number in E.164 form (e.g. +5511999998888). Optional. At most 20 digits."
          },
          "individual": {
            "$ref": "#/components/schemas/CustomerIndividual"
          },
          "business": {
            "$ref": "#/components/schemas/CustomerBusiness"
          },
          "verification_mode": {
            "type": "string",
            "description": "The verification posture for this customer (see `CustomerVerification.mode`).\nOptional: omitted, the organization's posture applies - `reliance` when\nyour organization holds the `kyc_reliance` product (the default for API\nintegrators), otherwise `full`.\n- `reliance` - you verified the person and attest it; the end-customer does\n  nothing. OZAV still screens against designated lists and assesses\n  economic capacity, and approves automatically once the profile is\n  complete, the partner document is `received` and screening is clear.\n  OZAV terms (`terms_session_id`) are not required. Requires the\n  `kyc_reliance` entitlement (`403 ENTITLEMENT_REQUIRED` otherwise).\n- `full` - OZAV verifies the end-customer in a hosted session\n  (`POST /verifications`: document, liveness, face match).\n",
            "enum": [
              "full",
              "reliance"
            ]
          },
          "monitoring": {
            "type": "boolean",
            "default": false,
            "description": "Enable ongoing designated-list monitoring for this customer."
          },
          "terms_session_id": {
            "type": "string",
            "description": "The id (`ts_…`) of an accepted terms-of-service session. Not required\nfor a `reliance` customer (the default posture for API integrators):\nthe contract with the end-customer is yours. For `full` customers, mint\nit with `POST /customers/terms-sessions`, present `terms_url` to your\nend user, record the acceptance with `POST /customers/terms-sessions/accept`,\nthen pass the same id here. Consumed at provisioning for personas that\nsettle or hold funds: provisioning a settlement customer fails closed\nwith `422 TERMS_ACCEPTANCE_REQUIRED` when there is no accepted session,\nand the customer stays unprovisioned (`status: pending`). Not required\nfor compliance-only (verification-only) personas, which are never\nprovisioned at a settlement layer.\n\nSandbox: a test key never resolves this field - the attestation is\nsimulated and any value (or none) is accepted. The gate is real only on\n`live`. Do not read a sandbox `201` as proof that your terms flow works.\n"
          },
          "self_custody": {
            "$ref": "#/components/schemas/CustomerSelfCustody"
          },
          "custody_model": {
            "type": "string",
            "enum": [
              "managed",
              "self_custody"
            ],
            "description": "Which custody to provision for this customer, when your organization\nhas both custody products enabled.\n\nOmit it and the default applies: `self_custody` when your\norganization has it, otherwise `managed`. That default is why this\nfield exists - before it, holding both products removed the choice\ninstead of offering it, and every customer came out `self_custody`\n(no deposit address, and no custody balance for an off-ramp to\ndebit).\n\nAsking for a model your organization has not contracted returns\n`403 ENTITLEMENT_REQUIRED` naming the missing product - never a\nsilent fallback to the other model. When it resolves to\n`self_custody`, `self_custody.address` is required.\n"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "CustomerSelfCustody": {
        "type": "object",
        "required": [
          "chain",
          "address"
        ],
        "description": "Bring-your-own-wallet block for self-custody provisioning. Supply the\nend-user-controlled wallet address on the given chain; OZAV records it and\nnever opens a managed wallet for it (the end-user holds the key). Consumed\nonly when your organization holds the `self_custody` product; when the\nprovisioning plan resolves to self-custody and this block is missing, the\ncall fails closed with `422 SELF_CUSTODY_ADDRESS_REQUIRED` and the customer\nstays unprovisioned. The resulting wallet reports\n`custody_model: self_custody`.\n\n**How the plan resolves, and what to do when you did not ask for\nself-custody.** The create request does not choose a custody model. The\nmodel comes from the products your organization holds, and **self-custody\noutranks managed custody when both are enabled** - so an organization with\nboth gets self-custody on every customer, and a request without this block\nis refused even though it never asked for self-custody.\n\nIf you meant managed custody, that refusal is not something you can fix in\nthe request body: ask OZAV to disable `self_custody` for your organization.\nThe `422` message says this too, so the response is self-explanatory.\n\n**This block accepts more chains than `POST /wallets` does.** The two\nchain lists are different: the enum here is the wider one,\nand `POST /wallets` accepts a narrower set. Registering a second address\nlater on one of the chains only this block accepts is refused. If a chain\nmatters to you and it is not in the `POST /wallets` enum, ask before you\ndesign around it.\n",
        "properties": {
          "chain": {
            "type": "string",
            "enum": [
              "ethereum",
              "base",
              "optimism",
              "polygon",
              "avalanche_c_chain",
              "worldchain",
              "unichain",
              "linea",
              "sonic",
              "sei",
              "celo",
              "zksync_era",
              "solana",
              "tron",
              "stellar"
            ],
            "description": "The chain the end-user-controlled address lives on. Public API v2 accepts\nthe active EVM networks listed in `Network`, plus Solana, Tron, and\nStellar. The address must be valid on the chosen chain; OZAV records it\nas-is and never derives or holds a key for it.\n"
          },
          "address": {
            "type": "string",
            "minLength": 1,
            "description": "The end-user-controlled wallet address on `chain`."
          }
        }
      },
      "CustomerUpdateRequest": {
        "type": "object",
        "description": "Mutable customer attributes. Only the fields you include are changed; omitted fields are left untouched.",
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Replacement contact email. At most 254 characters."
          },
          "phone": {
            "type": "string",
            "maxLength": 32,
            "description": "Replacement contact phone number (E.164). At most 20 digits."
          },
          "individual": {
            "$ref": "#/components/schemas/CustomerIndividual"
          },
          "business": {
            "$ref": "#/components/schemas/CustomerBusinessUpdate"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "CustomerBusinessUpdate": {
        "type": "object",
        "description": "The `business` block of `PATCH /customers/{customer_id}`. The same fields as on creation, every one optional (send only what changes), plus the acceptance of the BRL account opening terms, which only exists here because the associated person who accepts is created after the customer.",
        "properties": {
          "legal_name": {
            "type": "string",
            "description": "Registered legal name of the company."
          },
          "trade_name": {
            "type": "string",
            "description": "Trading / doing-business-as name, if different from the legal name."
          },
          "registration": {
            "$ref": "#/components/schemas/Registration"
          },
          "incorporation_date": {
            "type": "string",
            "format": "date",
            "description": "Date the company was incorporated (ISO 8601 date)."
          },
          "legal_structure": {
            "type": "string",
            "description": "Legal form of the entity (e.g. Ltda, LLC, S.A.). Accepted and stored on the customer record, and it is not requested in `requirements.missing_fields`: no automated assessment reads it today, so it is offered for completeness rather than asked for. For Brazil the field an onboarding actually reads is `company_type`."
          },
          "company_type": {
            "type": "string",
            "description": "Company classification. For Brazil the onboarding values are `MEI` (microempreendedor individual) and `PJ` (pessoa jurídica). Required for Brazilian onboarding; optional at creation."
          },
          "industry": {
            "type": "string",
            "description": "Primary industry or line of business."
          },
          "cnae_codes": {
            "type": "array",
            "maxItems": 20,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "pattern": "^([0-9]{7}|[0-9]{4}-[0-9]/[0-9]{2})$",
              "example": "6201-5/01"
            },
            "description": "Brazilian CNAE activity codes the company declares, either as 7 digits (`6201501`) or in the notation of the CNPJ card (`6201-5/01`). They are stored as digits only; `6201-5/01` and `6201501` are the same code, and the stored list never repeats one (`uniqueItems` describes the stored list: a request that repeats a code is accepted and stored once, and the 20-code limit counts codes after repeats are removed). The first one is the primary activity. Send an empty array to clear the list. Used only when the public registry (Receita Federal) does not return an activity code for the company's CNPJ; the registry always prevails, and a declared code is recorded as declared by the customer, never as official."
          },
          "website": {
            "type": "string",
            "format": "uri",
            "description": "Company website URL."
          },
          "phone": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Phone"
              }
            ],
            "description": "Structured contact phone for the company (dialing code + number). Required for Brazilian onboarding."
          },
          "registered_address": {
            "$ref": "#/components/schemas/Address"
          },
          "source_of_funds": {
            "type": "string",
            "description": "Declared source of the funds the business will move. Reported in `requirements.missing_fields` until supplied."
          },
          "expected_monthly_volume_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Expected monthly transaction movement in USD. This is the amount the economic-capacity assessment measures; what it measures against is the declared capacity (`monthly_income_usd` for an individual, `annual_revenue_usd` for a business). Supplying this alone yields no assessment. Reported in `requirements.missing_fields` until supplied."
          },
          "annual_revenue_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Declared annual revenue in USD (amounts are USD-denominated). This is the capacity the expected monthly volume is measured against (the assessment reads it as `annual_revenue_usd / 12`): without it the economic-capacity assessment has nothing to compare and returns no verdict. `0` is a valid answer and is not treated as absent. Reported in `requirements.missing_fields` until supplied."
          },
          "expected_daily_volume_usd": {
            "type": "number",
            "minimum": 0,
            "description": "Expected daily transaction movement in USD. A separate declaration, not the monthly figure divided by anything: account opening asks for both, and a derived number would be a value the customer never declared. Reported in `requirements.missing_fields` until supplied."
          },
          "is_regulated_activity": {
            "type": "boolean",
            "description": "Whether the business performs a regulated activity. A declared value (including `false`) counts as answered; absent is reported in `requirements.missing_fields`. When `true`, supply `regulator` and `regulated_activity_details`."
          },
          "regulator": {
            "type": "string",
            "description": "Name of the supervising regulator when the activity is regulated."
          },
          "regulated_activity_details": {
            "type": "string",
            "description": "Detail of the regulated activity (licence, scope) when regulated."
          },
          "nature_of_business": {
            "type": "string",
            "description": "Description of what the business does / the services it provides."
          },
          "aml_program": {
            "type": "string",
            "description": "Description of the business's compliance / AML program. Accepted and stored on the customer record, and it is not requested in `requirements.missing_fields`: no automated assessment reads it today, so it is offered for completeness rather than asked for. "
          },
          "operating_jurisdictions": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            },
            "description": "Countries the business operates in (ISO 3166-1 alpha-2 codes). Accepted and stored on the customer record, and it is not requested in `requirements.missing_fields`: no automated assessment reads it today, so it is offered for completeness rather than asked for. Sanctions screening does not currently read this list."
          },
          "account_purpose": {
            "type": "string",
            "description": "Intended use of the account / purpose of the relationship."
          },
          "bc_protege_disabled": {
            "type": "boolean",
            "description": "Brazilian business only: the company declares it has turned off BC Protege+ (the Central Bank of Brazil service that blocks institutions from opening accounts in the company's name; see https://www.gov.br/pt-br/servicos/proteger-se-com-o-bc-protege), or never turned it on. Required before a BRL account is requested for a business: without `true`, `POST /accounts` refuses with `422 ACCOUNT_PROFILE_INCOMPLETE`, with `business.bc_protege_disabled` as an `error.details[].param` (code `missing_field`; sandbox too). Only the company can turn the service off, with its own gov.br login; send `true` once it has. Absent = not declared."
          },
          "account_opening_terms_accepted_by": {
            "type": "string",
            "writeOnly": true,
            "description": "Brazilian business only: the id of the associated person of this customer (pending or approved, not suspended) on whose behalf the business accepted the BRL account opening terms. The person needs a legal name and a valid Brazilian CPF in `individual.tax_id`; otherwise `422 REPRESENTATIVE_TAX_ID_REQUIRED` (a person created from the onboarding documents cannot be given a legal name or a CPF through the API: name another person or contact OZAV support). Any other id returns `422 REPRESENTATIVE_NOT_FOUND`. OZAV records the acceptance as declared by your organization through the API (when, and with which API key) and delivers the institution's term on its next account-opening pass. Sending the same person again, with the same name and CPF, records no new acceptance (the update itself still applies and emits `customer.updated`); a different person, or a corrected name or CPF, records a new one. One declared with a `test` key counts in the sandbox only. The field is write-only and never returned. When the institution that opens the account asks for these terms, `POST /accounts` refuses a BRL account without the acceptance with `422 ACCOUNT_PROFILE_INCOMPLETE`, with `business.account_opening_terms_accepted_by` as an `error.details[].param` (code `missing_field`; sandbox too). You can send it before `POST /accounts` for any Brazilian business: when the institution does not ask for the terms, the acceptance is recorded and has no effect."
          }
        }
      },
      "TermsSession": {
        "type": "object",
        "description": "A terms-of-service session. Customer provisioning at a settlement partner is fail-closed on one of these being accepted.",
        "required": [
          "object",
          "status",
          "terms_url",
          "terms_version",
          "expires_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The session token, prefixed `ts_`. Returned only on creation - OZAV stores just its hash. Persist it; it is never returned again, not even on an idempotent replay."
          },
          "object": {
            "type": "string",
            "enum": [
              "terms_session"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "accepted"
            ],
            "description": "`pending` until you record the acceptance; only `accepted` satisfies `POST /customers`. There is no hosted page in between, so there is no intermediate state."
          },
          "terms_url": {
            "type": "string",
            "format": "uri",
            "description": "The terms to present to your end user."
          },
          "terms_version": {
            "type": "string",
            "description": "The version in force when the session was created. Acceptance is recorded against this version, not the current one."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time",
            "description": "Present once the acceptance has been recorded."
          }
        }
      },
      "TermsSessionAcceptRequest": {
        "type": "object",
        "description": "Your attestation that the end user accepted the terms, with the evidence you collected.",
        "required": [
          "terms_session_id",
          "accepted_by"
        ],
        "properties": {
          "terms_session_id": {
            "type": "string",
            "description": "The `id` returned by `POST /customers/terms-sessions`."
          },
          "accepted_by": {
            "type": "object",
            "description": "Who accepted.",
            "required": [
              "legal_name"
            ],
            "properties": {
              "legal_name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            }
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time",
            "description": "When they accepted. ISO-8601 with an explicit UTC offset, not in the future. Defaults to the time of this call."
          },
          "ip_address": {
            "type": "string"
          },
          "user_agent": {
            "type": "string"
          }
        }
      },
      "Customer": {
        "type": "object",
        "description": "An end-user you serve - an individual or a business. The anchor resource that every other object references by its `customer_id`.",
        "required": [
          "id",
          "object",
          "type",
          "status"
        ],
        "properties": {
          "limits": {
            "description": "The limits that apply to this customer, returned only by `GET /customers/{customer_id}`; lists and create responses leave it out. `null` means the limits could not be read right now - retry, and never read it as zero. Amounts are in USD.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "organization",
                  "customer"
                ],
                "properties": {
                  "organization": {
                    "description": "The limits contracted for your organization, shared by all your customers. `null` when no organization limit is set.",
                    "oneOf": [
                      {
                        "type": "null"
                      },
                      {
                        "type": "object",
                        "required": [
                          "onramp_request_max_usd",
                          "withdraw_tier_usd",
                          "withdraw_daily_max_usd",
                          "since"
                        ],
                        "properties": {
                          "onramp_request_max_usd": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "The largest single purchase request."
                          },
                          "withdraw_tier_usd": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "Withdrawals up to this amount need no manual approval."
                          },
                          "withdraw_daily_max_usd": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "The most that can be withdrawn per day."
                          },
                          "since": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time",
                            "description": "When these limits took effect."
                          }
                        }
                      }
                    ]
                  },
                  "customer": {
                    "description": "This customer's own limit currently in force. `null` when none is in force.",
                    "oneOf": [
                      {
                        "type": "null"
                      },
                      {
                        "type": "object",
                        "required": [
                          "monthly_usd",
                          "per_transaction_usd",
                          "since"
                        ],
                        "properties": {
                          "monthly_usd": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "The most this customer can move per calendar month."
                          },
                          "per_transaction_usd": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "The most per single operation."
                          },
                          "since": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time",
                            "description": "When this limit took effect."
                          }
                        }
                      }
                    ]
                  }
                }
              }
            ]
          },
          "id": {
            "type": "string",
            "description": "Opaque customer identifier, prefixed `ozav_cus_`."
          },
          "object": {
            "type": "string",
            "const": "customer",
            "description": "Resource type discriminator; always the literal `customer`."
          },
          "type": {
            "type": "string",
            "description": "Whether the customer is a natural person (`individual`) or a company (`business`).",
            "enum": [
              "individual",
              "business"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/CustomerStatus"
          },
          "account_status": {
            "type": "string",
            "enum": [
              "active",
              "pending"
            ],
            "description": "Whether the account can operate - a different question from whether the verification passed, which is `status`. `active` means named accounts, cards and payouts are available. `pending` means they are not, and the calls that need them refuse with `422 ACCOUNT_NOT_ACTIVATED`.\nAn approved customer can sit at `pending` for two different reasons. When data is missing, `requirements.due` names it and `requirements.owner` reads `integrator` - send the field with `PATCH /customers/{customer_id}` and the account activates in the same response. When `due` is empty, the account is simply not provisioned yet and nothing is owed by you.\nRead this field instead of inferring operability from `status`: an approved customer is not necessarily one that can move money."
          },
          "decision": {
            "$ref": "#/components/schemas/CustomerDecision"
          },
          "sla": {
            "$ref": "#/components/schemas/CustomerSla"
          },
          "external_ref": {
            "type": "string",
            "description": "Your own identifier for this customer, as supplied at creation."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Contact email for the customer."
          },
          "phone": {
            "type": "string",
            "description": "Contact phone number (E.164)."
          },
          "individual": {
            "$ref": "#/components/schemas/CustomerIndividual"
          },
          "business": {
            "$ref": "#/components/schemas/CustomerBusiness"
          },
          "verification": {
            "$ref": "#/components/schemas/CustomerVerification"
          },
          "requirements": {
            "$ref": "#/components/schemas/Requirements"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the customer was created (RFC 3339 / ISO 8601, UTC)."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the customer was last modified (RFC 3339 / ISO 8601, UTC)."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "still_required": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerDocumentType"
            },
            "description": "The documents this customer will need before an account can be opened for them,\ngiven their type and country of registration. Returned on `POST /customers` so you\nlearn the list when the customer is created, not when the account is refused.\n\nThis is not `requirements.documents_due`, and the difference matters. `documents_due`\nis what OZAV has formally asked for, each item with a deadline and a reason; right\nafter creation it is empty, because nothing has been asked yet. `still_required` is\nwhat the corridor will demand regardless - a Brazilian company needs seven documents\n(plus four per associated person, reported by `GET /customers/{customer_id}/documents`\nin `still_required_by_person`) whether or not anyone has asked.\n\nAbsent, never `[]`, for a customer whose requirement set is not modelled: an empty\nlist would assert \"nothing is missing\", and that promise is only safe to make for the\nshapes the gate actually models. The same field, computed the same way, is returned by\n`GET /customers/{customer_id}/documents` once documents start arriving.\n"
          }
        }
      },
      "PendingDocumentsOfPerson": {
        "type": "object",
        "description": "One associated person and the documents that person still owes.",
        "required": [
          "associated_person_id",
          "still_required",
          "pending"
        ],
        "properties": {
          "associated_person_id": {
            "type": "string",
            "description": "The person's public id (`ozav_per_…`), as returned by `GET /customers/{customer_id}/associated-persons`.",
            "example": "ozav_per_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
          },
          "name": {
            "type": "string",
            "description": "The person's full name, when recorded."
          },
          "still_required": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerDocumentType"
            }
          },
          "pending": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PendingDocument"
            }
          }
        }
      },
      "PendingDocument": {
        "type": "object",
        "description": "One document category the customer still owes, and why it is still owed.\n\n**The whole point of this object is the difference between its two\nstates.** `missing` means the bytes never arrived - upload the document.\n`rejected` means they arrived and failed review - fix the document and\nupload it again. `still_required` cannot tell you which, so an\nintegrator reading only that field resends the same rejected file.\n\nThe category vocabulary is the same `doc_type` you use to upload, so\nwhat you read here is what you send back.\n",
        "required": [
          "doc_type",
          "state"
        ],
        "properties": {
          "doc_type": {
            "$ref": "#/components/schemas/CustomerDocumentType"
          },
          "state": {
            "type": "string",
            "enum": [
              "missing",
              "rejected"
            ],
            "description": "`missing` - never uploaded, or declared and never uploaded. A document counts only once its bytes arrive, so one you created but never finished uploading is `missing`, not `rejected`.\n\n`rejected` - uploaded and failed review. Do not resend the same file; correct it first."
          },
          "reason": {
            "type": "string",
            "description": "Why the document was rejected, in text meant for a person to act on. Present only when `state` is `rejected`, and only when a reason was recorded: some older rejections have none, and OZAV does not invent one. Its absence never means the document was accepted - `state` is what says that.",
            "example": "Issued more than 90 days ago."
          }
        },
        "example": {
          "doc_type": "proof_of_address",
          "state": "rejected",
          "reason": "Issued more than 90 days ago."
        }
      },
      "CustomerDocumentType": {
        "type": "string",
        "description": "The kind of KYC/KYB document. Jurisdiction-agnostic - generic\ncategories, never country-specific artifact names -\nbecause this API onboards your customers worldwide.\n\n**What this list is, and what it is not.** It is the vocabulary for\ndeclaring what you are uploading. It is not a per-country list of which\nartifact satisfies which category: whether a given national ID, driving\nlicence or utility bill is accepted for a specific customer is decided\nin review, not by this enum, and OZAV does not publish a per-jurisdiction\nacceptance table. Declare the category that best describes the artifact\nand upload it; if something is missing or unusable you get a named\noutcome on the verification, not a silent rejection.\n\nThe fields a customer must supply are a separate question, answered by\n`requirements.missing_fields` on the customer itself. That set is driven\nby the country on `tax_id` / `registration`, and it does not vary by the\ncurrency of any account you later open.\n\n**Required versus optional, and why nothing here blocks.** For an\ninternational account the receiving institution asks for a defined set of\ncompany information. Some categories below satisfy part of that set and\nsome do not apply to every company - a Brazilian company, for example, has\nno separate memorandum of association, because its Contrato Social is both\nthe constitutive act and the articles.\n\nSo this API accepts every category at any time and refuses none of them for\nbeing absent. Upload what the company has, under the category that\ndescribes it.\n\n**What this API does not do yet, said plainly.** `requirements` on\n`GET /customers/{id}` reports missing onboarding *fields*, not missing\ndocuments - it will not tell you which of the categories above are still\noutstanding. Document review happens at the receiving institution, and\ntoday its outcome reaches you through your account contact, not through\nthis API. Publishing the list here without saying that would suggest a\ncompleteness signal that does not exist.\n\nA company that cannot produce one of these is not blocked from being\ncreated. It is blocked, later and visibly, from having an account opened -\nand the difference matters: the first is a wall at the door, the second is\na list you can work through.\n\n**Three categories for the Brazilian named account.** `tax_registration`\nis the company's tax registration certificate (in Brazil, the Cartão\nCNPJ). `pep_declaration` is a politically-exposed-person declaration,\none per partner or legal representative (`subject_type:\nassociated_person`), optional except for a BRL account. `income_tax_return` is the company's income tax\nreturn, accepted as evidence of economic capacity alongside\n`financial_statement`. A bank statement is **not** accepted as economic\ncapacity by the Brazilian receiving institution; send a balance sheet or\nthe tax return instead.\n",
        "enum": [
          "identity_document",
          "selfie",
          "liveness",
          "proof_of_address",
          "company_registration",
          "ownership_structure",
          "financial_statement",
          "source_of_funds",
          "bank_statement",
          "authorized_signature_form",
          "banking_reference",
          "corporate_resolution",
          "good_standing_certificate",
          "source_of_funds_certification",
          "tax_registration",
          "pep_declaration",
          "income_tax_return",
          "other"
        ]
      },
      "CustomerDocumentUploadRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "doc_type"
        ],
        "properties": {
          "subject_type": {
            "type": "string",
            "enum": [
              "customer",
              "associated_person"
            ],
            "default": "customer",
            "description": "Who this document belongs to. Omit for customer-scoped documents."
          },
          "associated_person_id": {
            "type": "string",
            "description": "Required when subject_type is associated_person. Opaque `ozav_ap_...` id."
          },
          "doc_type": {
            "$ref": "#/components/schemas/CustomerDocumentType"
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Free-text label describing what this file is, in your own words. Strongly recommended whenever `doc_type` is `other`, because without it the document is indistinguishable from any other `other` in review. It complements `doc_type` and never replaces it: the type is what drives requirements, the label is what a human reads.",
            "example": "Articles of Association (2025 amendment)"
          },
          "side": {
            "type": "string",
            "enum": [
              "front",
              "back",
              "single"
            ],
            "description": "Document side when applicable."
          },
          "issuing_country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "ISO 3166-1 alpha-2 issuing country when applicable."
          },
          "mime": {
            "type": "string",
            "description": "MIME type of the file to upload (e.g. application/pdf, image/png). Optional integrity hint.",
            "example": "application/pdf"
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 1,
            "description": "Declared file size in bytes. Optional integrity hint."
          },
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "Lowercase hex SHA-256 of the file. Optional integrity hint; rejected if malformed."
          }
        }
      },
      "LimitRequestCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "monthly_usd"
        ],
        "properties": {
          "monthly_usd": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,2})?$",
            "description": "The monthly limit you ask for, in USD, as a decimal string.",
            "example": "50000.00"
          },
          "per_transaction_usd": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{1,12}(\\.\\d{1,2})?$",
            "description": "Optional per-transaction limit, in USD. Cannot exceed `monthly_usd`.",
            "example": "10000.00"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Why the customer needs more. Short; no personal data."
          }
        }
      },
      "DestinationIdentificationRequest": {
        "type": "object",
        "description": "Who holds a destination wallet the customer sent crypto to. The vocabulary is the\none of `TravelRule.beneficiary`: `self_custodied` is the customer's own wallet,\n`external` is the customer's account at an exchange.\n",
        "required": [
          "chain",
          "address",
          "wallet_type"
        ],
        "properties": {
          "chain": {
            "type": "string",
            "description": "The network of the address, with the name the send used (e.g. `tron`, `ethereum`, `solana`). Any other spelling is refused with `422`, because it would never match the send.",
            "example": "tron"
          },
          "address": {
            "type": "string",
            "description": "The destination address. EVM addresses are matched case-insensitively.",
            "example": "TGssfnTouNs2H9AYPRkeZo9zuRxTPB5KCq"
          },
          "wallet_type": {
            "type": "string",
            "enum": [
              "self_custodied",
              "external"
            ],
            "description": "`self_custodied` - the customer's own wallet (holds the keys). `external` - the customer's account at an exchange."
          },
          "wallet_attested_ownership_at": {
            "type": "string",
            "format": "date-time",
            "description": "Required with `self_custodied` - when the customer attested owning the wallet. Must not be in the future."
          },
          "exchange": {
            "type": "object",
            "description": "Required with `external` - the exchange where the customer has the account.",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": "string",
                "maxLength": 140,
                "description": "Legal or trade name of the exchange."
              },
              "lei": {
                "type": "string",
                "pattern": "^[0-9A-Z]{20}$",
                "description": "The exchange's Legal Entity Identifier, when known."
              }
            }
          }
        }
      },
      "DestinationIdentification": {
        "type": "object",
        "description": "A destination wallet identified for a customer. It applies to every send of that\ncustomer to the same `chain` and `address`, past and future. Posting again for the\nsame wallet records a new identification; the latest one applies. It does not\nallow or block any send.\n",
        "required": [
          "object",
          "customer_id",
          "chain",
          "address",
          "wallet_type",
          "declared_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "destination_identification",
            "description": "Resource type discriminator; always `destination_identification`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the customer."
          },
          "chain": {
            "type": "string",
            "description": "The network, lowercase."
          },
          "address": {
            "type": "string",
            "description": "The address as stored (EVM in lowercase)."
          },
          "wallet_type": {
            "type": "string",
            "enum": [
              "self_custodied",
              "external"
            ],
            "description": "Who holds the wallet."
          },
          "wallet_attested_ownership_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Present with `self_custodied`."
          },
          "exchange": {
            "type": "object",
            "description": "Present with `external`.",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Name of the exchange."
              },
              "lei": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "LEI of the exchange, when given."
              }
            }
          },
          "declared_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the identification was recorded."
          },
          "requirement_satisfied": {
            "type": "boolean",
            "description": "Only on the create response - `true` when it answered an open `destination_unidentified` item of the customer."
          }
        }
      },
      "DestinationIdentificationList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list",
            "description": "Always `list`."
          },
          "data": {
            "type": "array",
            "description": "The customer's identifications, newest first (up to 200).",
            "items": {
              "$ref": "#/components/schemas/DestinationIdentification"
            }
          }
        }
      },
      "CustomerDeactivation": {
        "type": "object",
        "description": "Whether you deactivated the customer through the API. It reflects only your own deactivation.",
        "required": [
          "object",
          "customer_id",
          "deactivated",
          "deactivated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "customer_deactivation",
            "description": "Resource type discriminator; always `customer_deactivation`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the customer."
          },
          "deactivated": {
            "type": "boolean",
            "description": "`true` while your deactivation is in force."
          },
          "deactivated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When you first deactivated the customer; `null` when active."
          }
        }
      },
      "LimitRequest": {
        "type": "object",
        "required": [
          "id",
          "object",
          "customer_id",
          "status",
          "requested",
          "reason",
          "documents_due",
          "approved_limit",
          "created_at",
          "updated_at",
          "decided_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "ozav_lir_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
          },
          "object": {
            "type": "string",
            "enum": [
              "limit_request"
            ]
          },
          "customer_id": {
            "type": "string",
            "example": "ozav_cus_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
          },
          "status": {
            "type": "string",
            "description": "`awaiting_documents` while a proof in `documents_due` is missing;\n`in_review` once everything arrived and OZAV is deciding;\n`approved` when the new limit is in force (see `approved_limit`);\n`declined` when OZAV did not raise the limit (no reason is given);\n`canceled` when the customer stopped being eligible (rejected, closed\nor suspended) while the request was open.",
            "enum": [
              "awaiting_documents",
              "in_review",
              "approved",
              "declined",
              "canceled"
            ]
          },
          "requested": {
            "type": "object",
            "required": [
              "monthly_usd",
              "per_transaction_usd"
            ],
            "properties": {
              "monthly_usd": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "50000.00"
              },
              "per_transaction_usd": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "10000.00"
              }
            }
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "documents_due": {
            "type": "array",
            "description": "The proofs of economic capacity this request still needs. Send each\none with `POST /customers/{customer_id}/documents` using its\n`doc_type`. Empty once the request is decided.",
            "items": {
              "type": "object",
              "required": [
                "key",
                "doc_type",
                "label",
                "status"
              ],
              "properties": {
                "key": {
                  "type": "string",
                  "example": "bank_statement"
                },
                "doc_type": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "bank_statement"
                },
                "label": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "description": "`due` - send it; `submitted` - received, under review; `accepted` - an OZAV review accepted a document sent after the request.",
                  "enum": [
                    "due",
                    "submitted",
                    "accepted"
                  ]
                }
              }
            }
          },
          "approved_limit": {
            "description": "The limit now in force. Only when `status` is `approved`; it can be lower than requested.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "monthly_usd",
                  "per_transaction_usd"
                ],
                "properties": {
                  "monthly_usd": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "40000.00"
                  },
                  "per_transaction_usd": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "example": "10000.00"
                  }
                }
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "CustomerDocument": {
        "type": "object",
        "description": "A customer's uploaded supporting document. Never exposes the raw storage location.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque document id.",
            "example": "ozav_doc_01J8Z9K2M3N4P5Q6R7S8T9U0V1"
          },
          "object": {
            "type": "string",
            "enum": [
              "document"
            ],
            "description": "Resource type discriminator; always the literal `document`."
          },
          "customer_id": {
            "type": "string",
            "description": "The customer this document belongs to."
          },
          "subject_type": {
            "type": "string",
            "enum": [
              "customer",
              "associated_person"
            ],
            "description": "Whose document this is. `customer` - the customer itself. `associated_person` - one of a business customer's associated persons, named in `associated_person_id`."
          },
          "associated_person_id": {
            "type": "string",
            "description": "Present when subject_type is associated_person."
          },
          "doc_type": {
            "$ref": "#/components/schemas/CustomerDocumentType"
          },
          "side": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "front",
              "back",
              "single",
              null
            ],
            "description": "Which face of a two-sided document these bytes are. `single` for a document captured in one image. `null` where the document type has no sides (a PDF statement)."
          },
          "issuing_country": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 3166-1 alpha-2 country that issued the document, when known. `null` when the document type has no issuer (a proof of address) or it was not supplied."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_upload",
              "received",
              "rejected"
            ],
            "description": "`pending_upload` = grant issued, bytes not yet confirmed; `received` = bytes present; `rejected` = failed review."
          },
          "mime": {
            "type": [
              "string",
              "null"
            ],
            "description": "Content type of the stored bytes, as observed on upload. `null` while `status` is `pending_upload`."
          },
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Size of the stored bytes. `null` while `status` is `pending_upload`."
          },
          "sha256": {
            "type": [
              "string",
              "null"
            ],
            "description": "SHA-256 of the stored bytes, computed by OZAV on receipt - not a value you send. Compare it against your own digest to prove the upload was not truncated. `null` while `status` is `pending_upload`."
          },
          "review": {
            "$ref": "#/components/schemas/DocumentReview"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the upload grant was created (RFC 3339, UTC), not when bytes arrived."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed (RFC 3339, UTC) - the receipt of bytes and a review outcome both move it."
          }
        }
      },
      "DocumentReview": {
        "type": "object",
        "description": "OZAV's own review of a supporting document (proof of address, bank\nstatement, proof of income, company document). Identity documents and\nselfies are verified in the hosted verification session instead and\nread `not_reviewed` here.\n\n`rejected` comes with a closed `reason_code` and its English label; a\nfresh `requirements.documents_due` line follows with a new deadline. The\n`document.reviewed` webhook carries the same outcome. Omitted (not\n`not_reviewed`) when the review could not be read at response time.\n",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "accepted",
              "rejected",
              "in_review",
              "not_reviewed"
            ],
            "description": "- `not_reviewed` - no review yet (or not a document OZAV reviews).\n- `in_review` - waiting on a compliance officer.\n- `accepted` - the document satisfies the requirement.\n- `rejected` - refused; see `reason_code`. Send a new file.\n"
          },
          "reason_code": {
            "$ref": "#/components/schemas/DocumentReviewReasonCode"
          },
          "reason": {
            "type": "string",
            "description": "English label of `reason_code`. Present only with `reason_code`."
          },
          "reviewed_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the current review was recorded. Absent while `not_reviewed`."
          }
        }
      },
      "DocumentReviewReasonCode": {
        "type": "string",
        "description": "Why a document was rejected. Closed vocabulary; each code has a fixed English label in `review.reason`.",
        "enum": [
          "unreadable",
          "not_the_requested_document",
          "holder_mismatch",
          "holder_is_not_the_company",
          "holder_in_other_script",
          "expired",
          "address_mismatch",
          "low_confidence",
          "tampering_signals",
          "missing_period",
          "not_registered",
          "registry_mismatch",
          "outdated_version",
          "ownership_not_evidenced",
          "holder_shares_address",
          "officer_decision"
        ]
      },
      "CustomerCredential": {
        "type": "object",
        "description": "A portable verification credential (IETF SD-JWT, `vc+sd-jwt`) signed by OZAV. Give `credential` to the end user; keep `id` to look the credential up at `status_url`.",
        "required": [
          "id",
          "object",
          "customer_id",
          "format",
          "credential",
          "kyc_level",
          "method",
          "issued_at",
          "expires_at",
          "jwks_url",
          "status_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque credential id (the JWT `jti`).",
            "example": "ozav_vc_Q2xhdWRlRmFibGU1MQ"
          },
          "object": {
            "type": "string",
            "enum": [
              "credential"
            ],
            "description": "Resource type discriminator; always the literal `credential`."
          },
          "customer_id": {
            "type": "string",
            "description": "The customer this credential attests."
          },
          "format": {
            "type": "string",
            "enum": [
              "vc+sd-jwt"
            ],
            "description": "Always `vc+sd-jwt` - an IETF SD-JWT with selectively disclosable claims."
          },
          "credential": {
            "type": "string",
            "description": "The compact SD-JWT, `<jwt>~<disclosure>~...~`. Hand it to the end user; do not store it as personal data - the disclosures carry the CPF, name and birth date in clear."
          },
          "kyc_level": {
            "type": "integer",
            "enum": [
              2,
              3
            ],
            "description": "`3` = hosted session (document, liveness, face match); `2` = document-based identification (reliance or manual review)."
          },
          "method": {
            "type": "string",
            "enum": [
              "hosted_session",
              "reliance",
              "manual_review"
            ],
            "description": "How OZAV verified the customer."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time",
            "description": "When this credential was issued (RFC 3339, UTC)."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "When this credential stops being valid regardless of the customer's status."
          },
          "jwks_url": {
            "type": "string",
            "format": "uri",
            "description": "Where OZAV publishes the signing keys (JWKS, `kid`-addressed). Public; no API key."
          },
          "status_url": {
            "type": "string",
            "format": "uri",
            "description": "Public status of this credential, `valid` | `revoked` | `expired` | `unknown`. No API key; no personal data."
          }
        }
      },
      "CustomerDocumentUpload": {
        "description": "A newly created document plus the short-lived signed URL to PUT the file bytes to.",
        "allOf": [
          {
            "$ref": "#/components/schemas/CustomerDocument"
          },
          {
            "type": "object",
            "properties": {
              "upload_url": {
                "type": "string",
                "description": "Short-lived upload URL - `PUT` the raw file bytes here. Not returned by the list\nendpoint.\n\n**Send your API key on this PUT as well.** The embedded `upload_token` scopes the\ngrant to this one customer/document pair and expires on its own, but it does not\nreplace authentication - a `PUT` without `X-API-Key` (or `Authorization: Bearer`)\nis rejected with `MISSING_API_KEY`.\n\nBecause the key is required, **this URL is for your server, not your browser**: do\nnot hand it to an end user's device, which would mean shipping your secret key with\nit. Upload the bytes from your backend.\n"
              },
              "upload_expires_at": {
                "type": "string",
                "format": "date-time",
                "description": "When `upload_url` expires."
              }
            }
          }
        ]
      },
      "AssociatedPersonCreateRequest": {
        "type": "object",
        "description": "A person associated with a business customer (UBO, director, officer, or signer), added for KYB.",
        "required": [
          "roles",
          "individual"
        ],
        "properties": {
          "roles": {
            "type": "array",
            "minItems": 1,
            "description": "One or more roles this person holds in the business:\n- `ubo` - ultimate beneficial owner (an owner above the ownership threshold).\n- `director` - a member of the board or a statutory director.\n- `officer` - an executive officer (e.g. CEO, CFO).\n- `authorized_signer` - authorized to act or sign on the account.\n",
            "items": {
              "type": "string",
              "enum": [
                "ubo",
                "director",
                "officer",
                "authorized_signer"
              ]
            }
          },
          "ownership_percent": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of the business this person owns, 0-100. Relevant for `ubo` roles."
          },
          "individual": {
            "$ref": "#/components/schemas/CustomerIndividual"
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Contact email for this person. Optional. At most 254 characters."
          }
        }
      },
      "AssociatedPersonUpdateRequest": {
        "type": "object",
        "description": "Mutable attributes of an associated person. Only the fields you include are changed.",
        "properties": {
          "roles": {
            "type": "array",
            "description": "Replacement set of roles - one or more of `ubo`, `director`, `officer`, `authorized_signer`.",
            "items": {
              "type": "string",
              "enum": [
                "ubo",
                "director",
                "officer",
                "authorized_signer"
              ]
            }
          },
          "ownership_percent": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Revised ownership percentage, 0-100."
          },
          "individual": {
            "$ref": "#/components/schemas/AssociatedPersonIndividualUpdate"
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Contact e-mail of the person. The institution that opens the account asks for it from every representative of a company. It can be set or replaced here at any time, also on a person created from the onboarding documents (which accepts only `email`, `individual.phone`, `individual.gender` and, when missing, `individual.residential_address` and `individual.date_of_birth`)."
          }
        }
      },
      "AssociatedPerson": {
        "type": "object",
        "description": "A person linked to a business customer for KYB purposes.\n\n`verification.mode` is **inherited from the customer**, never set per\nperson: an associated person has no verification posture of its own, so\na customer onboarded under `reliance` reports `reliance` here too.\n\nWhen the customer has no recorded posture, `mode` is **omitted**. Absent\nis not `full`: it means OZAV has no posture on record for that customer,\nand `status` must not be read as a verification attestation while `mode`\nis absent.\n",
        "required": [
          "id",
          "object",
          "roles"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque associated-person identifier, prefixed `ozav_ap_`."
          },
          "object": {
            "type": "string",
            "const": "associated_person",
            "description": "Resource type discriminator; always the literal `associated_person`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the business customer this person belongs to."
          },
          "roles": {
            "type": "array",
            "description": "Roles this person holds - one or more of `ubo`, `director`, `officer`,\n`authorized_signer` (see `AssociatedPersonCreateRequest.roles`).\n",
            "items": {
              "type": "string",
              "enum": [
                "ubo",
                "director",
                "officer",
                "authorized_signer"
              ]
            }
          },
          "ownership_percent": {
            "type": "number",
            "description": "Percentage of the business this person owns, 0-100."
          },
          "individual": {
            "$ref": "#/components/schemas/CustomerIndividual"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Contact email for this person."
          },
          "verification": {
            "$ref": "#/components/schemas/CustomerVerification"
          },
          "requirements": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Requirements"
              }
            ],
            "description": "What is still missing **on this person** before an account can be opened\nfor the customer they belong to.\n\nA business account is refused while a qualifying owner is incomplete, and\nthat refusal names the person. This field is the same information delivered\nwhen you create the person, instead of when you request the account.\n\nThe set depends on the customer's **jurisdiction**. A\nperson attached to a Brazilian company owes the full KYB set:\n`individual.legal_name`, `individual.date_of_birth`, `individual.tax_id`,\n`email`, `individual.phone` and `individual.residential_address`, with the\naddress complete: `line1`, `number`, `neighborhood`, `city`, `state` and\n`postal_code`. Outside Brazil only `individual.legal_name` and\n`individual.date_of_birth` are owed.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the associated person was added (RFC 3339 / ISO 8601, UTC)."
          }
        }
      },
      "ExchangeConditions": {
        "type": "object",
        "description": "Public exchange conditions. `spread_bps` is the additional\nclient-selected spread for the quote, in integer basis points. OZAV cost,\nfloor, margin, split and cost analytics are internal and are not\nserialized in public API responses.\n",
        "required": [
          "spread_bps"
        ],
        "properties": {
          "spread_bps": {
            "type": "integer",
            "minimum": 0,
            "description": "Additional client-selected spread, in integer basis points (100 bps = 1%)."
          },
          "rate_without_spread": {
            "type": "string",
            "description": "The same quote before the `spread_bps` you selected, in the same\nscale as `rate`. When you did not select a spread it equals `rate`\nexactly.\n\n\nUse it to tell the spread you added apart from the OZAV rate. If you are\ncomparing our quote to a market reference, compare\n`rate_without_spread`: `rate` already carries whatever spread you\nasked for.\n\n\n**To read your spread back exactly, measure both against the\nquote, not against each other.** Both rates are derived from the same\ninternal base, so dividing one by the other returns slightly more\nthan `spread_bps` (with a 10 bps floor, a 40 bps spread reads as\n40.04). The value that is exact is `spread_bps` itself.\n\n\nAbsent when the quote does not allow deriving it. OZAV cost, floor\nand margin stay inside both rates and are never serialized.\n"
          },
          "amount_deducted": {
            "type": "string",
            "description": "The amount taken off `source.amount` before conversion, in the\nsource currency. Present only when an execution cost (the delivery\nnetwork cost, the local payment rail cost and, on purchases, the\nsettlement safety margin) is charged as an amount instead of being\ncarried by the rate. Today that is limited to\nstablecoin purchases (fiat to USDT/USDC) on accounts configured for\nit, so its presence depends on the account, not on the corridor alone.\nThe network cost depends on `target.chain`: send it. Sandbox does\nnot emit it yet.\n\n\nOn a transfer created from such a quote, `amounts.rate` is the all-in\nrate (`target.amount / source.amount`, 8 decimal places), so the\ntransfer reconciles on its own, to that precision, without this field.\n\n\nWith it, `(source.amount - amount_deducted) * rate` equals\n`target.amount`. That is the reconciliation to run when both are\npresent. Absent, the plain `source.amount * rate` holds, as before.\n\n\n**It is published at the source currency's accounting precision,\nrounded UP.** The reconciliation therefore closes to within one minor\nunit of the source, and never resolves to more than `target.amount`\npromises. Rounding it down would let the arithmetic overstate what\nyou receive.\n\n\n**It is not an OZAV charge, and it is not our cost.** It is what\nwas deducted from this quote. OZAV cost, floor and margin stay\ninternal and are never serialized.\n"
          }
        }
      },
      "Quote": {
        "type": "object",
        "description": "An FX quote - opaque, indicative (`floating`), and carrying the applied exchange conditions. Execute it by creating a transfer that references its `id`.",
        "required": [
          "id",
          "object",
          "rate_type",
          "rate",
          "source",
          "target",
          "exchange_conditions",
          "expires_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque quote identifier, prefixed `ozav_q_`."
          },
          "object": {
            "type": "string",
            "const": "quote",
            "description": "Resource type discriminator; always the literal `quote`."
          },
          "customer_id": {
            "type": "string",
            "description": "Opaque customer id (prefixed `ozav_cus_`) the quote was created for."
          },
          "rate_type": {
            "type": "string",
            "description": "How the `rate` behaves until execution.\n\n- `floating` - indicative; the rate re-derives at execution time. **Every quote\n  returns this today**, and it comes with `expires_at: null`: a firm price holds\n  for only a few seconds, so OZAV re-quotes at execution instead of expiring your\n  quote. Treat `rate` as indicative: what\n  executes can differ from the quote.\n- `locked` - held fixed until `expires_at`. **Not served on this API yet.** When it\n  ships it will require the price-lock facility enabled on your account, and will\n  carry a real `expires_at`. Ask OZAV if you need a firm price today.\n\nDo not branch on `expires_at` being non-null: on `floating` it is always `null`,\nin sandbox and in production alike.\n",
            "enum": [
              "floating",
              "locked"
            ]
          },
          "rate": {
            "type": "string",
            "description": "The FX rate as a decimal string - target-currency units per one source unit, all-in except for any `amount_deducted` (it already carries any `spread_bps` you added). Indicative on a `floating` quote, which is every quote today: it re-derives at execution. On the quote, `source.amount x rate` gives `target.amount` (with `amount_deducted` present, `(source.amount - amount_deducted) x rate`)."
          },
          "source": {
            "$ref": "#/components/schemas/MonetaryAmount"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MonetaryAmount"
              }
            ],
            "description": "The amount on the target side at the quoted `rate`: `source.amount x rate`, or `(source.amount - amount_deducted) x rate` when `amount_deducted` is present. When you fix `target.amount` it stays as you sent it and `source.amount` carries the rate instead.\n\nLike the rate, it is indicative on a `floating` quote: what executes can differ.\n"
          },
          "exchange_conditions": {
            "$ref": "#/components/schemas/ExchangeConditions"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the quote expires (RFC 3339 / ISO 8601, UTC).\n\n**`null` on a `floating` quote is the normal answer, not a missing\nvalue, and it does not mean the quote is valid forever.** A floating\nrate is indicative: it re-derives at execution, so there is no window\nto \"accept\" it within. Quote as close to execution as you can, and\nread the rate you are given at execution as the one that applies.\n\nA timestamp appears only when the rate is held: a `locked` quote,\nwhich expires at this instant and is refused after it.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the quote was created (RFC 3339 / ISO 8601, UTC)."
          },
          "facility": {
            "$ref": "#/components/schemas/FacilityPreview"
          },
          "facility_unavailable": {
            "$ref": "#/components/schemas/FacilityUnavailable"
          },
          "via": {
            "type": "object",
            "description": "Present only on a quote requested with `via`: the stablecoin and the network the conversion passes through.",
            "required": [
              "currency",
              "chain"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "chain": {
                "type": "string"
              }
            }
          },
          "legs": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "description": "Present only on a quote requested with `via`. The two legs, in order: BRL → stablecoin, then stablecoin → target. The second leg is priced on the stablecoin the first delivers, rounded down to the cent; a remainder below 0.01 stays in your customer's wallet. The top-level `source` is the first leg's source and `target` the second leg's target.",
            "items": {
              "$ref": "#/components/schemas/QuoteLeg"
            }
          }
        }
      },
      "QuoteLeg": {
        "type": "object",
        "description": "One leg of a quote requested with `via`, in the same shape as a quote, without its own id or expiry.",
        "required": [
          "source",
          "target",
          "rate",
          "exchange_conditions"
        ],
        "properties": {
          "source": {
            "$ref": "#/components/schemas/MonetaryAmount"
          },
          "target": {
            "$ref": "#/components/schemas/MonetaryAmount"
          },
          "rate": {
            "type": "string",
            "description": "Target units per one source unit, for this leg."
          },
          "exchange_conditions": {
            "$ref": "#/components/schemas/ExchangeConditions"
          }
        }
      },
      "FacilityUnavailable": {
        "type": "object",
        "description": "**Why the rate-lock preview is not available for this account.**\n\nReturned instead of `facility` when the request set `facility_preview: true` and the facility cannot be offered. It never fails the quote - the quote is the product - it only explains the absence.\n\n**Read this instead of guessing at a missing `facility` block.** Branch on `reason`, which is the stable slug and the same vocabulary as `details.reason` on `422 FACILITY_LOCK_UNAVAILABLE`, so one mapping serves the preview and the refusal.\n\n**This preview runs the offer gates only** - the ones that decide whether a lock can be shown. It does not evaluate collateral or your contractual limits, because those are resolved at acceptance, inside the lock. So `collateral_insufficient` is **not** a value you will see here: it appears on `POST /transfers` instead. The table below is the complete set this endpoint can return.\n\n| `reason` | What it means | What to do | |---|---|---| | `client_not_exempt` | Your organization has not contracted the facility. | Talk to your account manager - no request will succeed until it is contracted. | | `no_client` | The key does not resolve to an organization the facility knows. | Contact support with the request id. | | `no_terms` / `terms_inactive` / `no_terms_version` / `invalid_terms` | The signed schedule (`Termo`) is missing, not active, or incomplete. | A contract step, not a parameter. Talk to your account manager. | | `past_client_cutoff` | Locking now is past today's cutoff. | Try earlier on the next business day. | | `client_window_too_short` | Today's cutoff is too close to leave a usable delivery window (under ten minutes). | Try earlier on the next business day. | | `client_window_exceeds_settlement` | Today's window does not fit settlement. | Ask for a shorter window, or try earlier. | | `unwind_not_ready` | The facility is not being offered right now. Not about your account. | Retry later; if it persists, contact support. | | `pool_config_missing` / `execution_margin_not_configured` / `settlement_mode_not_configured` | A platform setting the facility needs is missing. Not about your account. | Retry later; if it persists, contact support with the request id. | | `invalid_amounts` | The amounts on the quote cannot be used to price a lock. | Check `source.amount` and the target; if they look right, contact support. | | `previa_indisponivel` | The preview could not be computed. Not about your account, and not about your request. | The quote itself is valid and usable. If you need the preview, contact support with the `request_id` of this response. |\n",
        "readOnly": true,
        "required": [
          "reason",
          "message"
        ],
        "properties": {
          "reason": {
            "type": "string",
            "description": "Stable, machine-readable slug. **Branch on this, never on `message`.**"
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation in English, safe to show an operator. It includes `(reason: <slug>)`. May change without notice."
          }
        }
      },
      "FacilityPreview": {
        "type": "object",
        "description": "**The FX rate lock conditions (`trava de câmbio`), previewed alongside the quote.**\n\nReturned only when the request set `facility_preview: true` and your organization has the facility contracted and operable. Its `terms_version` is the value `POST /transfers` accepts as `facility_terms_version`, and this is the only place it comes from.\n\n**Absence is the answer, not an error.** A client without the facility gets a normal `201` quote with this block missing and no explanation - the preview never fails a quote, because the quote is the product. To learn why, attempt the transfer: it refuses loudly with `422 FACILITY_LOCK_UNAVAILABLE` and a machine-readable `details.reason`.\n\n**These numbers are contractual, not settings.** The penalty, the minimum charge, the reserve percentage, the client window and the cutoff come from the schedule (`Termo`) your organization signed. They are shown here because an acceptance over conditions the payer never saw does not support a charge later.\n",
        "readOnly": true,
        "properties": {
          "terms_version": {
            "type": "string",
            "description": "The version of the signed schedule these conditions come from. Echo it back as `facility_terms_version` on `POST /transfers`. It must match byte for byte."
          },
          "locked_rate": {
            "type": "number",
            "description": "The rate implied by this quote, at this instant - indicative. The binding lock is priced at acceptance (`POST /transfers` with `facility_terms_accepted: true`) from a firm quote, and what the customer receives comes back as `facility.amount` on the `201`, net of your organization's exchange conditions. Do not present `locked_rate` as the final price. The rate that would be locked, at this instant, always expressed as **BRL per unit of the stablecoin** - the same base the acceptance records. On an off-ramp (stable to BRL) it is `expected_amount / source_amount`; on an on-ramp (BRL to stable) it is `source_amount / expected_amount`. Never a ratio in the other direction."
          },
          "source_amount": {
            "type": "string",
            "description": "How much is being locked, in the source unit."
          },
          "source_asset": {
            "type": "string",
            "description": "The source unit of `source_amount`."
          },
          "expected_amount_minor": {
            "type": "string",
            "description": "What the customer receives at `locked_rate`, in the minor unit of `expected_amount_currency` - the quote's target currency: centavos (2 decimals) when the target is BRL, micro-units (6 decimals) when the target is a stablecoin such as USDC or USDT. A string, because the value can exceed what a JSON number holds exactly."
          },
          "expected_amount_currency": {
            "type": "string",
            "description": "The currency of `expected_amount_minor`: the quote's target. `BRL` on an off-ramp preview, the stablecoin on an on-ramp preview. Read it before interpreting the amount - the minor unit follows it."
          },
          "client_deadline_at": {
            "type": "string",
            "format": "date-time",
            "description": "By when the customer must deliver (pay the Pix, or hand over the asset). Missing it is what triggers the penalty below."
          },
          "settlement_deadline_at": {
            "type": "string",
            "format": "date-time",
            "description": "OZAV's own settlement deadline."
          },
          "settlement_mode": {
            "type": "string",
            "enum": [
              "T0",
              "T1"
            ],
            "description": "Settlement day for this lock."
          },
          "cutoff_time_brt": {
            "type": "string",
            "description": "The daily cutoff, in BRT, from the schedule. **It and the client window cut each other short: the deadline is always whichever comes first.** With a 120-minute window and a 17:00 cutoff, locking at 16:00 gives 60 minutes, not 120, and locking at 16:30 gives 30. That is the schedule, not a defect - but it is the first thing that surprises an integrator, so branch on `client_deadline_at`, never on the window length."
          },
          "charge_rule": {
            "type": "string",
            "description": "How the charge is composed if the customer does not deliver in time. Cumulative, and the penalty is the large part."
          },
          "penalty_pct": {
            "type": "number",
            "description": "Penalty, as a fraction. `0.05` is 5%."
          },
          "minimum_charge_usd": {
            "type": "number",
            "description": "The minimum charge per unsettled operation, in USD. **Not basis points** - an absolute amount."
          },
          "reserve_pct": {
            "type": "number",
            "description": "The reserve (collateral) required, as a fraction of the open position - aggregate, including the new one, not per transaction. `0.02` is 2%. It must be constituted before acceptance and maintained while positions are open; if the ratio falls below the schedule's minimum, a top-up call is triggered."
          },
          "holiday_risk": {
            "type": "boolean",
            "description": "`true` when the deadline crosses a business-day boundary the calculation cannot resolve against a holiday calendar. Treat the deadline as approximate and confirm."
          },
          "capacity_guaranteed": {
            "type": "boolean",
            "const": false,
            "description": "**Always `false`.** This preview prices the lock; it does not reserve room under the shared ceiling, which is resolved at acceptance. Reading headroom here and deciding later is exactly the non-atomic pre-check the pool exists to prevent - two clients would both see \"there is room\" and both proceed."
          }
        }
      },
      "TransferStatus": {
        "type": "string",
        "description": "Canonical transfer state machine. A successful FX transfer walks\n`awaiting_payment → payment_confirmed → converting → payout_initiated →\ncompleted`; the remaining states are terminal failures or attention states.\n\n| Value | Meaning |\n|---|---|\n| `awaiting_payment` | Transfer created; awaiting the customer's inbound payment. |\n| `payment_confirmed` | The inbound payment has been confirmed. |\n| `converting` | Funds are in flight and the FX conversion is underway (not yet confirmed). |\n| `payout_initiated` | The payout to the destination has been initiated. |\n| `completed` | Terminal success - funds delivered to the destination. |\n| `failed` | Terminal failure - no funds delivered. |\n| `cancelled` | Terminal - the transfer was cancelled. |\n| `expired` | Terminal - the transfer expired before completing. |\n| `needs_review` | Stuck and needs attention (e.g. underfunded, returned, in review); not terminal. |\n| `awaiting_approval` | Not returned: no transfer waits on approver keys. |\n",
        "enum": [
          "awaiting_payment",
          "payment_confirmed",
          "converting",
          "payout_initiated",
          "completed",
          "failed",
          "cancelled",
          "expired",
          "needs_review",
          "awaiting_approval"
        ]
      },
      "SettlementStatus": {
        "type": "string",
        "description": "Canonical per-leg settlement status. A transfer carries an independent\n`source_leg` and `destination_leg`, each with one of these values.\n\n| Value | Meaning |\n|---|---|\n| `pending` | Accepted; awaiting the payer's funds. Non-terminal. |\n| `processing` | Funds in flight (received, or payout initiated). Non-terminal. |\n| `needs_review` | Stuck / needs attention (underfunded, in review, returned). Non-terminal. |\n| `completed` | Terminal success - the leg settled. |\n| `failed` | Terminal failure - the leg did not settle. |\n| `cancelled` | Terminal - the leg was cancelled. |\n",
        "enum": [
          "pending",
          "processing",
          "needs_review",
          "completed",
          "failed",
          "cancelled"
        ]
      },
      "SettlementLeg": {
        "type": "object",
        "description": "The settlement state of one side of a transfer (source or destination). Read-only.",
        "readOnly": true,
        "properties": {
          "status": {
            "$ref": "#/components/schemas/SettlementStatus"
          },
          "settled_at": {
            "type": "string",
            "format": "date-time",
            "description": "When this leg reached terminal completion (RFC 3339 / ISO 8601, UTC). Present only once the leg is `completed`."
          }
        }
      },
      "TransferSource": {
        "description": "Where a transfer's funds come from. Discriminated by `type`; the value selects the flow together with the destination.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/TransferSourceFiatPayment"
          },
          {
            "$ref": "#/components/schemas/TransferSourceWallet"
          },
          {
            "$ref": "#/components/schemas/TransferSourceCryptoPayment"
          },
          {
            "$ref": "#/components/schemas/TransferSourceAccount"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "fiat_payment": "#/components/schemas/TransferSourceFiatPayment",
            "wallet": "#/components/schemas/TransferSourceWallet",
            "crypto_payment": "#/components/schemas/TransferSourceCryptoPayment",
            "account": "#/components/schemas/TransferSourceAccount"
          }
        }
      },
      "TransferSourceAccount": {
        "type": "object",
        "description": "The customer's own named fiat account (`ozav_acc_`) as the source of an on-ramp: the\ncustomer already deposited into that account, and the order is funded from its balance.\nNo payment instructions are returned, because there is nothing left to pay.\n\nThe account must be active and known to its institution, and the amount must fit the\nbalance that is not already committed to the customer's other open orders. In the sandbox,\na test key can use a sandbox account after `POST /accounts/{account_id}/simulate/credit`.\nChoose `fiat_payment` instead when the customer has not deposited yet and needs payment\ninstructions to send the money.\n",
        "required": [
          "type",
          "account_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "account",
            "description": "Discriminator for an own-account source; always the literal `account`."
          },
          "account_id": {
            "type": "string",
            "description": "The `ozav_acc_` id of the customer's own named fiat account."
          }
        }
      },
      "PixPaymentInstructions": {
        "type": "object",
        "description": "Pix payment instructions for a specific pay-in transaction.",
        "required": [
          "rail",
          "intent_id"
        ],
        "properties": {
          "rail": {
            "type": "string",
            "const": "pix",
            "description": "The payment rail; always `pix`."
          },
          "intent_id": {
            "type": "string",
            "description": "OZAV's id for the Pix pay-in intent. **Opaque - store it and echo it back, never parse it.** Unlike the `ozav_*` resource ids, this one carries no prefix and is a UUID today; the format is not part of the contract and may change. Use it to correlate the Pix payment with this transfer, not to infer anything about the intent."
          },
          "br_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pix copy-and-paste BR Code for this specific transaction."
          },
          "qr_code_image": {
            "type": [
              "string",
              "null"
            ],
            "description": "QR code image payload, when available."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the Pix instructions expire."
          }
        }
      },
      "TransferSourceFiatPayment": {
        "type": "object",
        "description": "A fiat payment the customer sends in (the FX / on-ramp source leg).",
        "required": [
          "type",
          "currency"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "fiat_payment",
            "description": "Discriminator for a fiat-payment source; always the literal `fiat_payment`."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "rail": {
            "$ref": "#/components/schemas/Rail"
          },
          "virtual_account_id": {
            "type": "string",
            "description": "Accepted and ignored: the payment is always received through the standard flow. Omit it."
          },
          "payment_instructions": {
            "readOnly": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/PixPaymentInstructions"
              }
            ],
            "description": "Returned for pay-in transfers after OZAV creates transaction-specific payment instructions. Clients display the Pix BR Code / QR to the payer; callers do not submit this field.\n"
          }
        }
      },
      "TransferSourceWallet": {
        "type": "object",
        "description": "A managed wallet as the source of funds (off-ramp / wallet transfer).",
        "required": [
          "type",
          "wallet_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "wallet",
            "description": "Discriminator for a wallet source; always the literal `wallet`."
          },
          "wallet_id": {
            "type": "string",
            "description": "The `ozav_wal_` id of the source wallet."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "chain": {
            "type": "string",
            "description": "The network the funds leave from, when the wallet serves more than one. Same rule as on a wallet destination: with the FX rate lock it is what the hedge follows, and an absent network refuses the lock instead of guessing."
          }
        }
      },
      "TransferSourceCryptoPayment": {
        "type": "object",
        "description": "An inbound crypto deposit as the source (crypto receive).",
        "required": [
          "type",
          "deposit_address_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "crypto_payment",
            "description": "Discriminator for a crypto-payment source; always the literal `crypto_payment`."
          },
          "deposit_address_id": {
            "type": "string",
            "description": "The id of the crypto deposit address the funds arrive at."
          }
        }
      },
      "TransferDestination": {
        "description": "Where a transfer's funds go. Discriminated by `type`; the value selects the flow together with the source.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/TransferDestinationPayout"
          },
          {
            "$ref": "#/components/schemas/TransferDestinationWallet"
          },
          {
            "$ref": "#/components/schemas/TransferDestinationCryptoAddress"
          },
          {
            "$ref": "#/components/schemas/TransferDestinationAccount"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "payout_destination": "#/components/schemas/TransferDestinationPayout",
            "wallet": "#/components/schemas/TransferDestinationWallet",
            "crypto_address": "#/components/schemas/TransferDestinationCryptoAddress",
            "account": "#/components/schemas/TransferDestinationAccount"
          }
        }
      },
      "TransferDestinationPayout": {
        "type": "object",
        "description": "A registered payout destination as the target (FX / off-ramp destination leg).",
        "required": [
          "type",
          "payout_destination_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "payout_destination",
            "description": "Discriminator for a payout-destination target; always the literal `payout_destination`."
          },
          "payout_destination_id": {
            "type": "string",
            "description": "The `ozav_pd_` id of the registered payout destination."
          }
        }
      },
      "TransferDestinationAccount": {
        "type": "object",
        "description": "The customer's own named fiat account (`ozav_acc_`) as the target of an off-ramp: the\nstablecoin is sold and the fiat is credited to that account, where it stays until the\ncustomer moves it. Available for USD, EUR and GBP accounts that are active and known to\nthe account's institution; the quote's target currency must be the account's currency.\n\nChoose this when the customer wants to hold a fiat balance rather than pay a third\nparty; choose `payout_destination` to pay out to a registered external account.\n",
        "required": [
          "type",
          "account_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "account",
            "description": "Discriminator for an own-account target; always the literal `account`."
          },
          "account_id": {
            "type": "string",
            "description": "The `ozav_acc_` id of the customer's own named fiat account."
          }
        }
      },
      "TransferDestinationWallet": {
        "type": "object",
        "description": "A managed wallet as the target (on-ramp / wallet transfer).",
        "required": [
          "type",
          "wallet_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "wallet",
            "description": "Discriminator for a wallet target; always the literal `wallet`."
          },
          "wallet_id": {
            "type": "string",
            "description": "The `ozav_wal_` id of the destination wallet."
          },
          "chain": {
            "type": "string",
            "description": "The network the credit lands on, when the wallet serves more than one. A managed wallet serves several networks, so a BRL Pix on-ramp into one requires it: without it the transfer is refused with `DESTINATION_CHAIN_REQUIRED` before any charge is created.\n\n**Optional in general, but effectively required when you use the FX rate lock.** The hedge OZAV buys is withdrawn to treasury on the same network as the delivery - buying on one and delivering on another would leave a crossing for someone to resolve with the exposure already open. With no network supplied there is nothing to follow, so the lock is **refused** (`facility.reason = sem_destino_de_tesouraria`) rather than guessed. The transfer itself still proceeds; only the lock is lost.\n\nThe refusal can also mean the pair itself has no registered treasury destination (`USDC:tron`, for instance, is not a pair OZAV serves). The message names the `asset:network` pair, so read it before assuming the field is at fault."
          }
        }
      },
      "TransferDestinationCryptoAddress": {
        "type": "object",
        "required": [
          "type",
          "address",
          "network",
          "currency"
        ],
        "description": "External crypto destination; triggers wallet screening and travel-rule requirements by policy.",
        "properties": {
          "type": {
            "type": "string",
            "const": "crypto_address",
            "description": "Discriminator for an external crypto-address target; always the literal `crypto_address`."
          },
          "address": {
            "type": "string",
            "description": "The external blockchain address to send to; must be valid on `network`."
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          }
        }
      },
      "PayoutDocumentUpload": {
        "type": "object",
        "description": "The supporting document for a payout, sent inline as base64.\n\nThe bytes travel in the request rather than through a signed upload URL\nbecause this is one small file per payout, and a single call leaves no\nwindow in which the document is registered and its bytes are absent,\na state that reads as \"sent\" and is not.\n",
        "additionalProperties": false,
        "required": [
          "file_name",
          "content_type",
          "content_base64"
        ],
        "properties": {
          "file_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "Name shown to compliance reviewers.",
            "example": "invoice-2026-08.pdf"
          },
          "content_type": {
            "type": "string",
            "description": "PDF only. The receiving institution named PDF as the format for the\ninvoice, and a file its compliance review cannot accept comes back as\na request for information days later, by email. Refusing here is an\nimmediate message to whoever still has the file in hand.\n",
            "enum": [
              "application/pdf"
            ]
          },
          "content_base64": {
            "type": "string",
            "minLength": 1,
            "description": "The file, base64-encoded, without a `data:` prefix. Maximum 10 MB\ndecoded. Content that does not decode, or decodes to zero bytes, is\nrefused with `400 DOCUMENT_CONTENT_INVALID` before anything is stored.\n",
            "example": "JVBERi0xLjQKJeLjz9MK"
          }
        }
      },
      "PayoutDocument": {
        "type": "object",
        "description": "The stored supporting document for a payout.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "object": {
            "type": "string",
            "enum": [
              "payout_document"
            ]
          },
          "transfer_id": {
            "type": "string",
            "description": "The transfer this document supports (`ozav_tr_...`)."
          },
          "file_name": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer",
            "description": "Decoded size."
          }
        }
      },
      "TravelRule": {
        "type": "object",
        "description": "Originator and beneficiary information for a crypto send, required by BCB\nResolution 520/2025 when value leaves custody to an external address.\n\n**Read the \"Required for an external crypto send\" list below before building\nthis object.** The fields marked required *there* are not the same as the ones\nmarked required *here*: this schema is shared with paths where Travel Rule is\nsupplementary, so it stays permissive. The mandatory set is enforced on\n`destination.type = crypto_address`, and a request that misses any of it is\nrefused with `422 TRAVEL_RULE_REQUIRED` and a `details.missing_fields` array\nnaming each one.\n\n### Required for an external crypto send\n\n| field | rule |\n|---|---|\n| `originator.name` | non-blank |\n| `originator.identity_documents[].type` | at least one entry must be accepted for the customer's country - see the table below |\n| `originator.identity_documents[].number` | non-blank; for `cpf` and `cnpj`, the check digits must be valid (`cnpj` accepts the alphanumeric format) - otherwise `missing_fields` names `originator.identity_documents.number(digito_verificador_invalido)` |\n| `originator.identity_documents[].issuing_country` | required on **every** entry |\n| `originator.address.street_line_1` | non-blank (`street` and `line1` are accepted aliases) |\n| `originator.address.city` | non-blank |\n| `originator.address.country` | non-blank |\n| `beneficiary.name` | non-blank |\n| `beneficiary.wallet_type` | `external` or `self_custodied` (`hosted` is rejected on this path) |\n| `beneficiary.wallet_attested_ownership_at` | ISO-8601 timestamp of when the holder attested ownership; must not be in the future |\n| `beneficiary.is_self` | boolean; `true` when the destination belongs to your own customer |\n\n#### Accepted originator identity documents, by country\n\nThe requirement follows the customer's country of residence as held on the\ncustomer record - **not** the country you send inside `travel_rule`, which is\nstored but never selects the regime. A country not listed below uses the\ndefault set.\n\nTo be precise about where that country comes from: you declare it when you\ncreate the customer (`individual.tax_id.country`, or the address), and you can\ncorrect it later with `PATCH /customers/{id}`. Both paths refuse a jurisdiction\nOZAV does not serve. So the regime is not selectable *per request*, but it does\nfollow what you declared for that customer - keep it accurate.\n\n| country | accepted `identity_documents[].type` |\n|---|---|\n| `BR` | `cpf`, `cnpj` |\n| `NG` | `bvn`, `nin`, `passport`, `national_id` |\n| *(default)* | `passport`, `national_id` |\n\nIf your customer's country is not listed and the default set does not fit\nits national identifiers, tell us and it gets modelled - the list is data,\nnot code.\n\nA destination that is an OZAV wallet id (`ozav_wal_...`) is exempt: the value\nstays in custody and both sides are already verified.\n\nValues inside `originator` and `beneficiary` must be plain strings - do not\nembed internal or third-party references.\n",
        "properties": {
          "originator": {
            "$ref": "#/components/schemas/TravelRuleParty"
          },
          "beneficiary": {
            "$ref": "#/components/schemas/TravelRuleParty"
          }
        }
      },
      "TravelRuleParty": {
        "type": "object",
        "description": "One side of a Travel Rule record. `required` here lists only what every path\nneeds; see `TravelRule` for the stricter set an external crypto send enforces.\n",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Full legal name of the party, as it appears on their verified identity."
          },
          "account": {
            "type": "string",
            "description": "The wallet address or account reference for this party."
          },
          "identity_documents": {
            "type": "array",
            "description": "Identity documents for this party. On an external crypto send the originator\nmust carry at least one entry whose type is accepted for the customer's\ncountry (see \"Accepted originator identity documents, by country\").\nAccepted under the legacy name\n`identifying_information` as well; both normalize to the same record.\n",
            "items": {
              "type": "object",
              "required": [
                "type",
                "number",
                "issuing_country"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "description": "Document kind, lowercase. Which kinds satisfy the originator\nrequirement depends on the customer's country - see \"Accepted\noriginator identity documents, by country\" above. Other kinds are\naccepted and stored but do not satisfy it on their own.\n",
                  "example": "cpf"
                },
                "number": {
                  "type": "string",
                  "description": "The document number. Non-blank. For `cpf` and `cnpj` on an external\ncrypto send, the check digits are validated; `cnpj` accepts the Receita\nFederal alphanumeric format (12 alphanumeric + 2 numeric check digits).\nPunctuation is ignored.\n",
                  "example": "11144477735"
                },
                "issuing_country": {
                  "type": "string",
                  "description": "ISO 3166-1 alpha-2 country that issued the document.",
                  "example": "BR"
                }
              }
            }
          },
          "address": {
            "type": "object",
            "description": "Postal address. Required for the originator on an external crypto send.\n`street_line_1` is the canonical key; `street` and `line1` are accepted aliases.\n",
            "properties": {
              "street_line_1": {
                "type": "string"
              },
              "street_line_2": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "state": {
                "type": "string"
              },
              "postal_code": {
                "type": "string"
              },
              "country": {
                "type": "string",
                "description": "ISO 3166-1 alpha-2.",
                "example": "BR"
              }
            }
          },
          "wallet_type": {
            "type": "string",
            "enum": [
              "hosted",
              "external",
              "self_custodied"
            ],
            "description": "How the party's wallet is held. On an external crypto send the beneficiary\nmust be `external` or `self_custodied`; `hosted` is rejected there.\n"
          },
          "wallet_attested_ownership_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the holder attested ownership of the wallet."
          },
          "is_self": {
            "type": "boolean",
            "description": "True when this party is your own customer (a send to their own external\nwallet) rather than a third party.\n"
          }
        }
      },
      "Transfer": {
        "type": "object",
        "description": "A money-movement resource. One resource serves every flow; the `(source.type, destination.type)` pair selects it.",
        "required": [
          "id",
          "object",
          "status",
          "source",
          "destination",
          "amounts"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque transfer identifier, prefixed `ozav_tr_`."
          },
          "object": {
            "type": "string",
            "const": "transfer",
            "description": "Resource type discriminator; always the literal `transfer`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the customer this transfer is for."
          },
          "quote_id": {
            "type": "string",
            "description": "The `ozav_q_` id of the executed quote, when the transfer references one."
          },
          "status": {
            "$ref": "#/components/schemas/TransferStatus"
          },
          "approval_id": {
            "type": "string",
            "description": "Not returned. No transfer waits on approver keys."
          },
          "status_detail": {
            "type": "object",
            "description": "Optional detail explaining the current status. Present for a transfer that needs OZAV reconciliation, and for one that ended badly (`failed`, `cancelled`, `expired`) once the reason was recorded; `code` is a catalog error code you can branch on. Absent when no reason was recorded, which is the case for transfers created before this field existed; treat absence as \"not stated\", never as \"no reason\". It never contains internal names, native rail error payloads, or a compliance verdict.",
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable OZAV status detail code."
              },
              "message": {
                "type": "string",
                "description": "Human-readable status detail."
              },
              "retryable": {
                "type": "boolean",
                "description": "Whether the client should retry the same action without OZAV intervention."
              }
            }
          },
          "source": {
            "$ref": "#/components/schemas/TransferSource"
          },
          "destination": {
            "$ref": "#/components/schemas/TransferDestination"
          },
          "source_leg": {
            "$ref": "#/components/schemas/SettlementLeg"
          },
          "destination_leg": {
            "$ref": "#/components/schemas/SettlementLeg"
          },
          "amounts": {
            "type": "object",
            "description": "The amounts and rate of this transfer. They start from the quote when the transfer is created (when the quote carried `exchange_conditions.amount_deducted`, `rate` is the all-in `target.amount / source.amount`, published at 8 decimal places, and differs from the quote's `rate` by the deducted cost), and on some paths they are updated at settlement: on the fiat-to-stablecoin path (`fiat_payment` to `wallet`) `source`, `target` and `rate` are rewritten from the settled amounts as the operation progresses, before the matching `transfer.*` webhook is sent. On the other paths they keep the quoted values, which are indicative on a `floating` quote. Read them again from `GET /transfers/{transfer_id}` or the webhook after the transfer settles; do not cache the values returned at creation.\n",
            "properties": {
              "source": {
                "$ref": "#/components/schemas/MonetaryAmount"
              },
              "target": {
                "$ref": "#/components/schemas/MonetaryAmount"
              },
              "rate": {
                "type": "string",
                "description": "The applied FX rate as a decimal string (target units per source unit). Absent for a same-currency move."
              }
            }
          },
          "exchange_conditions": {
            "$ref": "#/components/schemas/ExchangeConditions"
          },
          "travel_rule": {
            "$ref": "#/components/schemas/TravelRule"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the transfer was created (RFC 3339 / ISO 8601, UTC)."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the transfer was last updated (RFC 3339 / ISO 8601, UTC)."
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the transfer reached a terminal state (RFC 3339 / ISO 8601, UTC). Present once `status` is terminal."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "facility": {
            "$ref": "#/components/schemas/FacilityOutcome"
          },
          "settlement_references": {
            "$ref": "#/components/schemas/TransferSettlementReferences"
          },
          "beneficiary_bank": {
            "$ref": "#/components/schemas/TransferBeneficiaryBank"
          }
        }
      },
      "TransferSettlementReferences": {
        "type": "object",
        "readOnly": true,
        "description": "**The references the payment rail produced for this transfer**, the ones a bank or an auditor asks for to trace the payment. Returned by `GET /transfers/{transfer_id}` and `GET /transfers` for off-ramp transfers (a payout to a `payout_destination` or to your customer's own account) and for wallet sends (a transfer to a `wallet` or a `crypto_address`).\n\n**Only the keys that exist are present.** A reference appears once the payout step recorded it, never earlier and never made up; a key that does not apply to the rail is simply absent, and the whole object is absent while nothing has been recorded. Read the absence as \"not available (yet)\", never as \"failed\". The `transfer.*` webhook events and the `201` of a create do not carry it: read it again with `GET /transfers/{transfer_id}` after the transfer settles.\n\n| Key | Rail | What it is | |---|---|---| | `end_to_end_id` | Pix | The Pix end-to-end id (`E...`). | | `imad` | US wire | Input Message Accountability Data. | | `omad` | US wire | Output Message Accountability Data, when the wire reports it. | | `uetr` | SWIFT | The end-to-end transaction reference (UUID). | | `trace_number` | ACH | The ACH trace number. | | `ach_reference` | ACH | The ACH reference. | | `sepa_reference` | SEPA | The SEPA payment reference. | | `spei_reference` | SPEI | The SPEI tracking key. | | `tracking_number` | Other bank rails | The rail's tracking number. | | `tx_hash` | On-chain | The transaction hash, present only once the send is confirmed on-chain. | | `network` | On-chain | The network the transaction settled on (`ethereum`, `polygon`, `solana`, `tron`, `stellar`). Present with `tx_hash`. |\n",
        "properties": {
          "end_to_end_id": {
            "type": "string",
            "description": "Pix end-to-end id."
          },
          "imad": {
            "type": "string",
            "description": "US wire Input Message Accountability Data."
          },
          "omad": {
            "type": "string",
            "description": "US wire Output Message Accountability Data."
          },
          "uetr": {
            "type": "string",
            "description": "SWIFT end-to-end transaction reference."
          },
          "trace_number": {
            "type": "string",
            "description": "ACH trace number."
          },
          "ach_reference": {
            "type": "string",
            "description": "ACH reference."
          },
          "sepa_reference": {
            "type": "string",
            "description": "SEPA payment reference."
          },
          "spei_reference": {
            "type": "string",
            "description": "SPEI tracking key."
          },
          "tracking_number": {
            "type": "string",
            "description": "Tracking number of the bank rail."
          },
          "tx_hash": {
            "type": "string",
            "description": "On-chain transaction hash."
          },
          "network": {
            "type": "string",
            "description": "Network the on-chain transaction settled on."
          }
        }
      },
      "TransferBeneficiaryBank": {
        "type": "object",
        "readOnly": true,
        "description": "**The receiving bank of a fiat payout, masked.** Returned by `GET /transfers/{transfer_id}` and `GET /transfers` for off-ramp transfers sent to a bank account. Account numbers are never returned whole: `account_masked` is `****` plus the last four characters, and `iban_masked` keeps the country and check digits and the last four. Only the keys that are known are present; the object is absent when the destination is not a bank account (a Pix key, for instance) or the bank details were not recorded.\n",
        "properties": {
          "bank_name": {
            "type": "string",
            "description": "Name of the receiving bank."
          },
          "country": {
            "type": "string",
            "description": "Country of the receiving bank."
          },
          "routing_number": {
            "type": "string",
            "description": "US ABA routing number, a public bank identifier."
          },
          "account_masked": {
            "type": "string",
            "description": "Account number, last four characters only (`****1234`)."
          },
          "iban_masked": {
            "type": "string",
            "description": "IBAN with the middle masked (`DE89**************3000`)."
          },
          "bic": {
            "type": "string",
            "description": "SWIFT/BIC code of the receiving bank."
          }
        }
      },
      "FacilityOutcome": {
        "type": "object",
        "description": "**What happened to the FX rate lock on this transfer.**\n\nPresent on the `201` of a create that sent `facility_terms_accepted: true`, **on BRL/Pix flows**. A transfer created without the lock has no `facility` key at all, and its body is byte-for-byte what it has always been.\n\n**Two limits worth knowing before you build on it.**\n\n**It is a create-time outcome, not transfer state.** `GET /transfers/{id}` does not return it, and neither do the `transfer.*` webhook events after the first one. If you need it later, store it when you receive the `201`.\n\n**The lock applies to BRL/Pix on-ramp and off-ramp only.** The fields are accepted on every combination, but on a `crypto_address` destination, a wallet-to-wallet move, or the BRS redemption corridor they have no effect and no `facility` block comes back. Read the absence of the block on those flows as \"no lock\", not as \"locked\".\n\n**Read `locked` before you present a price to the payer.** A lock can be requested and still not happen, and those cases return `201`: the Pix charge is valid and the customer will pay it, so refusing would lose the operation for a condition that only costs the lock. The consequence is that this block is the only thing standing between \"the price is fixed\" and \"the price is whatever the market does before you pay\".\n\n| `state` | `locked` | What it means | What to do | |---|---|---|---| | `locked` | `true` | The rate is fixed until `deadline_at`. | Deliver before the deadline. Missing it triggers the schedule's penalty. | | `not_locked` | `false` | The lock did not happen and there is nothing to correct - typically the shared ceiling had no room, or a withdrawal was covered by the balance already delivered. The transfer is valid. | Nothing, but the conversion uses the rate at execution time. Tell whoever is paying. | | `refused` | `false` | The lock was refused by account state - conditions changed between the quote and the acceptance. | Read `reason`, fix it, and accept again. | | `undetermined` | `false` | The hedge may have executed; nothing was recorded as locked. | Do not retry and do not treat as failed. Reconcile with `GET /transfers/{id}`. |\n",
        "readOnly": true,
        "required": [
          "locked",
          "state"
        ],
        "properties": {
          "locked": {
            "type": "boolean",
            "description": "Whether the rate is actually fixed. The one field a payer's decision hangs on."
          },
          "state": {
            "type": "string",
            "enum": [
              "locked",
              "not_locked",
              "refused",
              "undetermined"
            ],
            "description": "The outcome. `not_locked` and `refused` are not synonyms and must not be collapsed: `refused` means the caller has something to correct, `not_locked` means there is nothing to do and the operation proceeds unlocked."
          },
          "reason": {
            "type": "string",
            "description": "Machine-readable slug for why the lock did not happen. Absent when `locked` is `true`. **This is the stable discriminator - branch on it, never on `message`.** Same vocabulary as `details.reason` on `422 FACILITY_LOCK_UNAVAILABLE`, so one mapping serves both the refusal and the degraded path."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation in English. May change without notice. Never parse it."
          },
          "deadline_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the locked rate expires. Present only when `locked` is `true`. This is the deadline as recorded, not one recalculated client-side - a second, locally computed deadline is not the one a charge would be based on."
          },
          "amount": {
            "type": "string",
            "description": "**What the customer receives** at the locked conditions, net of your organization's exchange conditions - the same number the floating flow would credit for this order, fixed at acceptance and delivered exactly. Present only when `locked` is `true`."
          },
          "currency": {
            "type": "string",
            "description": "The currency of `amount` and `counterparty_amount`. Present only when `locked` is `true`."
          },
          "counterparty_amount": {
            "type": "string",
            "description": "The gross amount OZAV bought to cover this lock (the exposure). Informational, for reconciliation. It is not what the customer receives - `amount` is. Present only when `locked` is `true`."
          }
        }
      },
      "AccountPayoutCreateRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "A payment out of the customer's own named account, in the account's\ncurrency, to one of the customer's registered payout destinations.\n",
        "required": [
          "amount",
          "destination",
          "reference"
        ],
        "properties": {
          "amount": {
            "type": "string",
            "description": "Decimal string with exactly two fractional digits (`\"50.00\"`). Never a float.",
            "pattern": "^(0|[1-9]\\d{0,14})\\.\\d{2}$",
            "examples": [
              "50.00"
            ]
          },
          "destination": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type",
              "payout_destination_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "payout_destination"
              },
              "payout_destination_id": {
                "type": "string",
                "description": "The `ozav_pd_` id of a destination registered for the same customer, in the same currency as the account."
              }
            }
          },
          "reference": {
            "type": "string",
            "description": "1-35 characters (letters, digits, space, `.`, `_`, `/`, `-`). Your\nidempotency lever: the same `reference` with the same `amount` on the\nsame account is the same payout. Also sent to the receiving bank as\nremittance information.\n",
            "pattern": "^[A-Za-z0-9 ._/-]{1,35}$"
          },
          "purpose": {
            "type": "string",
            "maxLength": 140,
            "description": "Optional purpose code or short description, passed to the payment institution when the rail carries one."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "AccountPayout": {
        "type": "object",
        "additionalProperties": false,
        "description": "A payment sent out of a customer's named account. `status` follows the\npayment institution: `accepted` means the order was taken, not that the\nmoney arrived; `settled` means it did; `rejected` is final; `pending`\nmeans the institution has not classified the outcome yet - read the\naccount's `/movements` rather than assuming either way.\n",
        "required": [
          "id",
          "object",
          "account_id",
          "destination",
          "amount",
          "currency",
          "reference",
          "status",
          "metadata"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque payout id, prefixed `ozav_apo_`. Stable for the same `reference` and `amount`."
          },
          "object": {
            "type": "string",
            "const": "account_payout"
          },
          "account_id": {
            "type": "string",
            "description": "The `ozav_acc_` id the money left."
          },
          "destination": {
            "type": "object",
            "required": [
              "type",
              "payout_destination_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "payout_destination"
              },
              "payout_destination_id": {
                "type": "string"
              }
            }
          },
          "amount": {
            "type": "string",
            "description": "Decimal string with two fractional digits."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "accepted",
              "settled",
              "rejected"
            ]
          },
          "purpose": {
            "type": "string"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "AccountCreateRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Request a named fiat receiving account for a customer whose supporting\ndocuments are already on file. `approved` is the verification result and\nis not sufficient on its own: this endpoint reads the documents, and on\n`live` it refuses with `422 ACCOUNT_PROFILE_INCOMPLETE` naming each one\nstill missing. Send them with `POST /customers/{customer_id}/documents`\nfirst. The sandbox does not enforce the documents (it does enforce, for\na Brazilian business, `business.bc_protege_disabled` and, when the\ninstitution asks for the terms, `business.account_opening_terms_accepted_by`),\nso an account opening there proves the request is well-formed, never\nthat the customer is ready.\n\nThe URL stays the same across currencies, countries and rails;\navailability is selected by the account rail matrix below.\n",
        "x-ozav-account-rail-availability": [
          {
            "currency": "BRL",
            "country": "BR",
            "rail": "pix"
          },
          {
            "currency": "USD",
            "country": "US",
            "rail": "fedwire"
          },
          {
            "currency": "EUR",
            "country": "EU",
            "rail": "sepa"
          },
          {
            "currency": "GBP",
            "country": "GB",
            "rail": "faster_payments"
          },
          {
            "currency": "MXN",
            "country": "MX",
            "rail": "spei"
          },
          {
            "currency": "USD",
            "country": "US",
            "rail": "swift"
          },
          {
            "currency": "USD",
            "country": "GB",
            "rail": "swift"
          }
        ],
        "required": [
          "customer_id",
          "currency"
        ],
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the approved customer receiving the account."
          },
          "account_type": {
            "type": "string",
            "const": "named_fiat_account",
            "description": "Account kind. API v2 exposes named fiat accounts here."
          },
          "currency": {
            "type": "string",
            "enum": [
              "BRL",
              "USD",
              "EUR",
              "GBP",
              "MXN"
            ],
            "description": "Account currency. BRL is the first sandbox-active fiat account currency."
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "Account country or region code. BR is active for BRL/Pix sandbox accounts."
          },
          "rail": {
            "$ref": "#/components/schemas/RequestRail",
            "description": "Receiving rail for this account."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "AccountStatus": {
        "type": "string",
        "description": "Public lifecycle state for a named account:\n- `pending` - opening is in progress or waiting for review.\n- `active` - receiving instructions can be shown to the customer.\n- `suspended` - account is blocked or deactivated.\n- `failed` - opening did not complete.\n",
        "enum": [
          "pending",
          "active",
          "suspended",
          "failed"
        ]
      },
      "AccountPixInstructions": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "key",
          "key_type"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Pix key customers can use to receive BRL into this account."
          },
          "key_type": {
            "type": "string",
            "enum": [
              "cpf",
              "cnpj",
              "email",
              "phone",
              "random",
              "evp"
            ],
            "description": "What kind of Pix key `key` is. Payer apps validate the key against its type, so publish the pair together - a correct key presented as the wrong type is refused."
          }
        }
      },
      "AccountBankInstructions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "bank_name": {
            "type": "string",
            "description": "Bank or payment institution name, when assigned."
          },
          "branch": {
            "type": "string",
            "description": "Branch number, when assigned."
          },
          "account_number": {
            "type": "string",
            "description": "Account number, including check digit when assigned."
          },
          "routing_number": {
            "type": "string",
            "description": "Domestic routing number for supported rails such as FedWire."
          },
          "iban": {
            "type": "string",
            "description": "IBAN for supported rails such as SEPA."
          },
          "bic": {
            "type": "string",
            "description": "BIC or SWIFT code, when assigned."
          },
          "sort_code": {
            "type": "string",
            "description": "UK sort code (six digits, usually shown as `00-00-00`). Present on `faster_payments`\naccounts.\n"
          },
          "clabe": {
            "type": "string",
            "description": "Mexican CLABE - 18 digits, including its own check digit. Present on `spei` accounts.\n"
          },
          "bre_b_key": {
            "type": "string",
            "description": "Colombian Bre-B key (the payer-facing alias). Present on `co_transfer` accounts.\n"
          },
          "account_type": {
            "type": "string",
            "enum": [
              "payment"
            ],
            "description": "The account category to state on a transfer form. `payment` is a payment account (conta de pagamento), which some payer flows require to be selected explicitly instead of checking or savings."
          }
        }
      },
      "AccountReceivingInstructions": {
        "type": "object",
        "additionalProperties": false,
        "description": "Rail-specific instructions to receive funds into this customer account.",
        "properties": {
          "pix": {
            "$ref": "#/components/schemas/AccountPixInstructions"
          },
          "bank_account": {
            "$ref": "#/components/schemas/AccountBankInstructions"
          }
        }
      },
      "PayoutDestinationCapabilities": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "receive"
        ],
        "properties": {
          "receive": {
            "type": "boolean",
            "description": "Whether OZAV can pay this destination right now.\n\nRead this, not `status`. A destination is `active` from the moment you create it, and\n`status` never says anything about readiness.\n\n- A **local BRL/Pix** destination (a Pix key) is paid directly to the key on the\n  domestic rail: `true` as soon as it is `active`, in `live` and in the sandbox.\n- Any other destination is `true` once it is registered with the settlement venue.\n  While it is `false`, a route that needs that registration is refused with\n  `422 PROVISIONING_REQUIRED`; the stablecoin-to-fiat off-ramp does not need it.\n\n`true` does not validate the Pix key itself: a key that does not exist, or whose\nholder does not match, still fails at payout time.\n"
          }
        }
      },
      "AccountCapabilities": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "receive",
          "send"
        ],
        "properties": {
          "receive": {
            "type": "boolean",
            "description": "Whether this account can currently receive on its configured rail."
          },
          "send": {
            "type": "boolean",
            "description": "Whether this account can currently initiate sends on its configured rail through API v2."
          }
        }
      },
      "AccountMovement": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "object",
          "direction",
          "amount",
          "currency",
          "occurred_at"
        ],
        "description": "One credit into a named fiat account, as the account's institution reports it. Amounts are decimal strings, never JSON numbers.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The entry's identifier. Stable across reads; use it to de-duplicate. It is the institution's own identifier where the statement is read live, and an OZAV id (`ozav_amov_...`) on a BRL account. The same id arrives in `account.credited`."
          },
          "object": {
            "type": "string",
            "const": "account_movement"
          },
          "direction": {
            "type": "string",
            "enum": [
              "credit"
            ],
            "description": "Only credits are listed today - money that arrived in the account."
          },
          "amount": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "description": "The amount the payer sent, as a decimal string in `currency`."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the institution booked the credit (RFC 3339, UTC)."
          },
          "reference": {
            "type": "string",
            "description": "The free text the payer sent with the transfer, when the rail carries one. Absent means the rail carried none."
          },
          "payer": {
            "type": "object",
            "additionalProperties": false,
            "description": "Who paid, when the institution reports it. Absent when it does not.",
            "properties": {
              "name": {
                "type": "string"
              },
              "document": {
                "type": "string",
                "description": "The payer's tax or registration document, as reported."
              }
            }
          },
          "received": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "amount",
              "currency"
            ],
            "description": "The amount that actually landed, when the institution reports it separately from `amount` (international wires lose correspondent charges). Absent means the institution reports one figure only.",
            "properties": {
              "amount": {
                "type": "string",
                "pattern": "^-?\\d+(\\.\\d+)?$"
              },
              "currency": {
                "$ref": "#/components/schemas/Currency"
              }
            }
          }
        }
      },
      "AccountBalance": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "available",
          "currency",
          "as_of"
        ],
        "description": "A balance reading mirrored from the institution that holds the account. Amounts are decimal strings in the account currency's own precision (two decimals for USD, EUR, GBP and BRL). Never a JSON number.",
        "properties": {
          "available": {
            "type": "string",
            "pattern": "^-?\\d+(\\.\\d+)?$",
            "description": "The balance available at `as_of`, as a decimal string (`\"965.00\"`)."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "as_of": {
            "type": "string",
            "format": "date-time",
            "description": "When OZAV took this reading (RFC 3339, UTC). Readings refresh every few minutes; a payment received after this instant is not yet reflected."
          }
        }
      },
      "Account": {
        "type": "object",
        "additionalProperties": false,
        "description": "A customer-owned named fiat account. The account id is OZAV-owned and\nremains stable.\n\n`currency`, `country` and `rail` describe the account that was actually\nopened, which may differ from the values in your request. OZAV selects\nthe banking route, and the receiving coordinates decide the rail: an\naccount reachable by IBAN is not reachable by the same instructions as\none reachable by a domestic account number. Read these three fields from\nthe response rather than assuming your request was echoed back, and\npublish `receiving_instructions` to your payers exactly as returned.\n",
        "required": [
          "id",
          "object",
          "customer_id",
          "account_type",
          "currency",
          "country",
          "rail",
          "status",
          "holder",
          "receiving_instructions",
          "capabilities",
          "balance",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque account identifier, prefixed `ozav_acc_`."
          },
          "object": {
            "type": "string",
            "const": "account",
            "description": "Resource type discriminator; always the literal `account`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the owning customer."
          },
          "account_type": {
            "type": "string",
            "const": "named_fiat_account",
            "description": "The only account kind v2 exposes today - a fiat account in the customer's own name. Present so a future kind is additive rather than a breaking change."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "ISO 3166-1 alpha-2 country of the account that was actually opened. OZAV picks the banking route, so read this back rather than assuming your request was echoed - see the note on this schema."
          },
          "rail": {
            "$ref": "#/components/schemas/Rail"
          },
          "status": {
            "$ref": "#/components/schemas/AccountStatus"
          },
          "holder": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "name",
              "type"
            ],
            "description": "Who the account is in the name of. Taken from the customer record, not from this request - the account cannot be opened in a name the customer has not been verified under.",
            "properties": {
              "name": {
                "type": "string",
                "description": "The legal holder name."
              },
              "type": {
                "type": "string",
                "enum": [
                  "individual",
                  "business"
                ],
                "description": "Mirrors the owning customer's type; a business customer's account is held in the company name, an individual's in the person's."
              }
            }
          },
          "receiving_instructions": {
            "$ref": "#/components/schemas/AccountReceivingInstructions"
          },
          "capabilities": {
            "$ref": "#/components/schemas/AccountCapabilities"
          },
          "balance": {
            "description": "The balance OZAV last read from the institution that holds the account, or `null` when OZAV has never\nread one for this account. Always present. `null` never means zero: it means no\nreading exists yet (a freshly opened account, or one whose institution does not expose a\nbalance). When present, `as_of` says when the reading was taken - the number is a\nmirror, not a live query, and a payment received after `as_of` is not reflected.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/AccountBalance"
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the account record was created (RFC 3339, UTC). Not when it became usable - `status` and `capabilities` decide that."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the account record last changed (RFC 3339, UTC)."
          },
          "pending_action": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type"
            ],
            "description": "Present only while `status` is `pending`, and only when something is actually being\nwaited on from the holder. `url` is omitted when the verification link cannot be\nverified as belonging to the institution that holds the account, in which case the action is still\nreported so the holder knows verification is outstanding.\n",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "complete_verification"
                ],
                "description": "What is being waited on. `complete_verification` - the holder must finish a verification step, at `url` when one could be published."
              },
              "url": {
                "type": "string",
                "format": "uri",
                "description": "Verification webview for the account holder. HTTPS, on a host of the institution that holds the account."
              }
            }
          },
          "failure_reason": {
            "type": "string",
            "description": "Present only while `status` is `failed`, and only when a reason was recorded. Absent\nmeans we did not record one. It never means \"a reason exists and we withheld it\".\n\nA comma-separated list of codes. Never free text, and never a message written by the\nunderlying institution. Every value is one of:\n\n- `billing_address`, `registered_address`, `company_documents`, `representatives`,\n  `representative_address`, `representative_documents`, `time_in_business`,\n  `tax_document`, `legal_name`: the holder still owes us this. Collect it and retry.\n- `counterparty_block`: the block is not the holder's paperwork. Sending more\n  documents will not clear it. Contact us.\n- `other_requirement`: a reason was recorded that we could not map to a code above.\n  When it appears alongside other codes, the rest of the list is not exhaustive.\n\nBranch on exact values, and treat an unrecognised value the way you treat\n`other_requirement`. The list grows. Every value is a member of\n`AccountFailureCode`, which carries the full enum.\n"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "AccountFailureCode": {
        "type": "string",
        "description": "One value of `Account.failure_reason`. That field carries a comma-separated list of these,\nso a value here is never the whole field.\n",
        "enum": [
          "billing_address",
          "registered_address",
          "company_documents",
          "representatives",
          "representative_address",
          "representative_documents",
          "time_in_business",
          "tax_document",
          "legal_name",
          "counterparty_block",
          "other_requirement"
        ]
      },
      "AccountPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "description": "The accounts on this page, newest first. Page through with `starting_after`; an empty array alongside `has_more` false means the end.",
                "items": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          }
        ]
      },
      "AccountHolder": {
        "type": "object",
        "description": "The legal owner of a payout-destination account.",
        "required": [
          "name",
          "type"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "The account holder's legal name."
          },
          "type": {
            "type": "string",
            "description": "Whether the account holder is a natural person (`individual`) or a company (`business`).",
            "enum": [
              "individual",
              "business"
            ]
          },
          "tax_id": {
            "$ref": "#/components/schemas/HolderTaxId"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsPix": {
        "type": "object",
        "description": "BRL / PIX payout destination. Provide a reusable PIX key; do not provide a one-time Pix BR Code.",
        "required": [
          "pix_key"
        ],
        "properties": {
          "pix_key": {
            "type": "string",
            "description": "A PIX key - CPF/CNPJ, email, phone number, or random (EVP) key."
          }
        }
      },
      "PayoutDestinationDetailsUsBank": {
        "type": "object",
        "description": "USD / fedwire. Older `ach` destinations carry the same shape.",
        "required": [
          "account_number",
          "routing_number"
        ],
        "properties": {
          "account_number": {
            "type": "string",
            "description": "The beneficiary bank account number."
          },
          "routing_number": {
            "type": "string",
            "description": "The 9-digit ABA routing number of the beneficiary bank."
          },
          "account_type": {
            "type": "string",
            "description": "The bank account type - `checking` or `savings`.",
            "enum": [
              "checking",
              "savings"
            ]
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsSepa": {
        "type": "object",
        "description": "EUR / sepa.",
        "required": [
          "iban"
        ],
        "properties": {
          "iban": {
            "type": "string",
            "description": "The beneficiary IBAN."
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsSwift": {
        "type": "object",
        "description": "swift - beneficiary reached by SWIFT wire. This is a payout destination shape; it is not the account-opening corridor of the same name, and `Rail.x-ozav-rail-availability` does not describe it.",
        "required": [
          "account_number",
          "bic"
        ],
        "properties": {
          "account_number": {
            "type": "string",
            "description": "The beneficiary account number or IBAN."
          },
          "bic": {
            "type": "string",
            "description": "The beneficiary bank's SWIFT/BIC code."
          },
          "intermediary": {
            "type": "object",
            "description": "Optional intermediary / correspondent bank details, when the route requires one.",
            "additionalProperties": true
          },
          "purpose_of_funds": {
            "type": "string",
            "description": "Purpose-of-payment statement required for some cross-border corridors."
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsSpei": {
        "type": "object",
        "description": "MXN / spei.",
        "required": [
          "clabe"
        ],
        "properties": {
          "clabe": {
            "type": "string",
            "description": "The 18-digit CLABE identifying the beneficiary account."
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name. Payouts in MXN carry it, so send it."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsFasterPayments": {
        "type": "object",
        "description": "GBP / faster_payments - beneficiary reached by UK Faster Payments. This is a payout destination shape; it is not the account-opening corridor of the same name, and `Rail.x-ozav-rail-availability` does not describe it.",
        "required": [
          "account_number",
          "sort_code"
        ],
        "properties": {
          "account_number": {
            "type": "string",
            "description": "The 8-digit UK account number."
          },
          "sort_code": {
            "type": "string",
            "description": "The 6-digit UK sort code."
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsCoTransfer": {
        "type": "object",
        "description": "COP / co_transfer. Not offered and not requestable; this is the shape of older destinations that carry it.",
        "required": [
          "account_number",
          "bank_code"
        ],
        "properties": {
          "account_number": {
            "type": "string",
            "description": "The beneficiary bank account number."
          },
          "bank_code": {
            "type": "string",
            "description": "The Colombian bank code identifying the beneficiary bank."
          },
          "account_type": {
            "type": "string",
            "description": "The account type (e.g. ahorros, corriente)."
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationBeneficiaryBank": {
        "type": "object",
        "description": "The beneficiary's bank - accepted on every rail that has one, and required in several corridors.",
        "properties": {
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsIbanLocal": {
        "type": "object",
        "description": "AED / uaefts - the beneficiary account is an IBAN, and the bank is identified by its BIC (the corridor carries no clearing-system code).",
        "required": [
          "iban",
          "bic"
        ],
        "properties": {
          "iban": {
            "type": "string",
            "description": "The beneficiary IBAN."
          },
          "bic": {
            "type": "string",
            "description": "The beneficiary bank's SWIFT/BIC. Required: no clearing system identifies the bank in this corridor. Checked when you register the destination (`BIC_REQUIRED` / `BIC_INVALID`, ISO 9362), not at payment time - a destination that is missing it would otherwise fail after the quote is locked."
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetailsLocalBank": {
        "type": "object",
        "description": "A domestic account in a corridor with no shape of its own - the account number plus whatever identifies the bank there. Serves `cnaps` (CNY), `zengin` (JPY), `nip` (NGN), `cce` (PEN), `rtc` (ZAR) and `eft` (CAD).\n\n**Which of the optional fields a corridor actually needs is the corridor's rule, not this schema's.** Registering a destination that serves one corridor must not be invalidated because another asks for more, so the requirement is enforced when the payment is created, with a named refusal (except `bic` in **PEN**, checked when you register the destination):\n\n- `bank_code` - required in **NGN** and **JPY**, from a closed directory\n  (669 and 1,150 values). The refusal lists the\n  accepted values. Accepted and unconstrained elsewhere.\n\n- `branch_code` - required in **JPY**; the payment is refused without\n  it.\n\n- `bic` - required in **PEN**, where no clearing system identifies the\n  bank. A missing or malformed one is refused when you register the\n  destination (`400 BIC_REQUIRED` / `400 BIC_INVALID`).\n\n- `account_number` alone is enough in **CNY** and **ZAR**.",
        "required": [
          "account_number"
        ],
        "properties": {
          "account_number": {
            "type": "string",
            "description": "The beneficiary account number, as the corridor writes it."
          },
          "bank_code": {
            "type": "string",
            "description": "The identifier of the beneficiary bank in the corridor's own scheme (CNAPS in China, the NIBSS code in Nigeria, the Zengin bank code in Japan)."
          },
          "branch_code": {
            "type": "string",
            "description": "The beneficiary bank branch. Required in Japan."
          },
          "bic": {
            "type": "string",
            "description": "The beneficiary bank's SWIFT/BIC. Required in Peru, and checked when you register the destination."
          },
          "bank_name": {
            "type": "string",
            "description": "The beneficiary bank's name."
          },
          "bank_address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "PayoutDestinationDetails": {
        "description": "Rail-specific beneficiary details; the applicable schema is selected by `rail`.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsPix"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsUsBank"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsSepa"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsSwift"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsSpei"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsFasterPayments"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsCoTransfer"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsIbanLocal"
          },
          {
            "$ref": "#/components/schemas/PayoutDestinationDetailsLocalBank"
          }
        ],
        "x-ozav-payout-rail-details": {
          "pix": "PayoutDestinationDetailsPix",
          "ach": "PayoutDestinationDetailsUsBank",
          "fedwire": "PayoutDestinationDetailsUsBank",
          "sepa": "PayoutDestinationDetailsSepa",
          "swift": "PayoutDestinationDetailsSwift",
          "spei": "PayoutDestinationDetailsSpei",
          "faster_payments": "PayoutDestinationDetailsFasterPayments",
          "co_transfer": "PayoutDestinationDetailsCoTransfer",
          "uaefts": "PayoutDestinationDetailsIbanLocal",
          "eft": "PayoutDestinationDetailsLocalBank",
          "cnaps": "PayoutDestinationDetailsLocalBank",
          "zengin": "PayoutDestinationDetailsLocalBank",
          "nip": "PayoutDestinationDetailsLocalBank",
          "cce": "PayoutDestinationDetailsLocalBank",
          "rtc": "PayoutDestinationDetailsLocalBank"
        }
      },
      "PayoutDestinationCreateRequest": {
        "type": "object",
        "description": "Register a payout destination for a customer on a specific rail. The `details` shape is selected by `rail`.",
        "required": [
          "customer_id",
          "currency",
          "rail",
          "account_holder",
          "details"
        ],
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the customer this destination belongs to."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "rail": {
            "$ref": "#/components/schemas/RequestRail"
          },
          "account_holder": {
            "$ref": "#/components/schemas/AccountHolder"
          },
          "details": {
            "$ref": "#/components/schemas/PayoutDestinationDetails"
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "PayoutDestination": {
        "type": "object",
        "description": "Where a customer's funds land off-ramp - a bank account, PIX key, or other rail endpoint.",
        "required": [
          "id",
          "object",
          "currency",
          "rail",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque payout-destination identifier, prefixed `ozav_pd_`."
          },
          "object": {
            "type": "string",
            "const": "payout_destination",
            "description": "Resource type discriminator; always the literal `payout_destination`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the owning customer."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "rail": {
            "$ref": "#/components/schemas/Rail"
          },
          "status": {
            "type": "string",
            "description": "Whether the destination has been deactivated by you:\n- `active` - not deactivated.\n- `inactive` - deactivated; reactivate it before using it again.\n\n**`active` does not mean the destination is ready to receive a\ntransfer.** A newly created destination is `active` immediately, but\nstays unusable until OZAV finishes registering it with the settlement\nrail. Using one before then is refused with\n`422 PROVISIONING_REQUIRED` (\"Complete destination registration\nfirst\"), and no field on this resource distinguishes the two states\nyet. Until one exists, treat `422 PROVISIONING_REQUIRED` as retryable\nrather than as a bad request, and do not gate your UI on `status`\nalone.\n",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "capabilities": {
            "$ref": "#/components/schemas/PayoutDestinationCapabilities"
          },
          "account_holder": {
            "$ref": "#/components/schemas/AccountHolder"
          },
          "details": {
            "$ref": "#/components/schemas/PayoutDestinationDetails"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the destination was registered (RFC 3339 / ISO 8601, UTC)."
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          }
        }
      },
      "CardCreateRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "customer_id"
        ],
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the approved customer receiving the card."
          },
          "type": {
            "type": "string",
            "enum": [
              "virtual",
              "physical"
            ],
            "default": "virtual",
            "description": "Card form factor. `physical` is reserved and currently returns `CARD_TYPE_NOT_AVAILABLE`."
          },
          "funding_type": {
            "type": "string",
            "enum": [
              "prepaid_balance"
            ],
            "default": "prepaid_balance",
            "description": "Cards spend the USDC in the cardholder's OZAV-managed wallet."
          },
          "brand_name": {
            "type": "string",
            "maxLength": 32,
            "description": "Optional brand label shown for white-label programs."
          },
          "replacement_for": {
            "type": "string",
            "description": "Existing `ozav_card_` id being replaced."
          },
          "replacement_reason": {
            "type": "string",
            "enum": [
              "damaged",
              "expired",
              "lost",
              "stolen"
            ],
            "description": "Why the previous card is being replaced. Send it alongside `replacement_for`; on its own it describes nothing."
          },
          "spending_controls": {
            "$ref": "#/components/schemas/CardSpendingControls"
          }
        }
      },
      "CardSpendingControls": {
        "description": "Per-card rules applied at authorisation time. A merchant category or country the acquirer does not send is treated as unknown: an allow list declines it, because \"only these\" cannot be satisfied by a value nobody can name, while a block list permits it.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "allowed_categories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Merchant category codes (MCC) permitted on this card."
          },
          "blocked_categories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Merchant category codes (MCC) refused on this card."
          },
          "allowed_merchant_countries": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "ISO 3166-1 alpha-2 countries permitted. An authorization whose merchant country the acquirer does not send is declined while this list is set - see the note on this schema."
          },
          "blocked_merchant_countries": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "ISO 3166-1 alpha-2 countries refused. An unknown merchant country is permitted here, which is the opposite of the allow list."
          },
          "allowed_card_presences": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "present",
                "not_present"
              ]
            },
            "description": "Restricts to card-present (terminal) or card-not-present (online) purchases. Leaving it unset permits both."
          },
          "blocked_card_presences": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "present",
                "not_present"
              ]
            },
            "description": "Refuses card-present or card-not-present purchases. Setting `[not_present]` is the usual way to make a physical card useless online if stolen."
          },
          "spending_limits": {
            "type": "array",
            "description": "Rolling caps evaluated at authorization time. Each entry pairs an amount with the window it applies to; several entries apply together and the tightest one decides. Read the `amount` note - it is the one field on this API in minor units.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "amount",
                "interval"
              ],
              "properties": {
                "amount": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "Minor units."
                },
                "interval": {
                  "type": "string",
                  "description": "Reset period, aligned to the UTC calendar rather than a rolling window: daily resets at 00:00 UTC, weekly on Monday, monthly on the 1st, yearly on 1 January. all_time never resets, and per_authorization applies to each transaction on its own. Totals are built from authorised amounts, so a later reversal does not return headroom within the same period.",
                  "enum": [
                    "per_authorization",
                    "daily",
                    "weekly",
                    "monthly",
                    "yearly",
                    "all_time"
                  ]
                },
                "categories": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Merchant category codes (MCC) this limit applies to. Omit to cover all spend on the card. A transaction whose category the acquirer does not send falls outside a scoped limit, so a category budget never silently becomes a card-wide one."
                }
              }
            }
          }
        }
      },
      "Card": {
        "type": "object",
        "description": "Canonical OZAV card object. Sensitive card data is accessed only through secure display sessions.",
        "required": [
          "id",
          "object",
          "customer_id",
          "type",
          "status",
          "funding_type",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque card identifier, prefixed `ozav_card_`."
          },
          "object": {
            "type": "string",
            "const": "card",
            "description": "Resource type discriminator; always the literal `card`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the cardholder customer."
          },
          "type": {
            "type": "string",
            "enum": [
              "virtual",
              "physical"
            ],
            "description": "Fixed at creation and never changes. A `physical` card additionally requires a shipping flow; a `virtual` card is usable as soon as it reaches `active`."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active",
              "frozen",
              "cancelled",
              "expired"
            ],
            "description": "`pending` - created, not yet usable. It becomes `active` when the card processor\nconfirms the card, with no call of yours, and you receive `card.updated`. A card the\nprocessor returns already usable is born `active`, and in the sandbox every card is.\n`active` - authorizations are attempted.\n`frozen` - reversible refusal, set by `/freeze` and undone by `/unfreeze`.\n`cancelled` - terminal, set by `/close`; there is no reopen, issue a replacement.\n`expired` - past `exp_month`/`exp_year`.\n\n`active` is not the same as \"will approve\". A card with no funded balance is `active`\nand declines every purchase with `Insufficient balance` - see `funding_type`.\n"
          },
          "funding_type": {
            "type": "string",
            "enum": [
              "prepaid_balance"
            ],
            "description": "How authorizations are funded. Only `prepaid_balance` exists today: the card spends a\nbalance that must already be there.\n\nNote, because it decides whether you can pilot this: in the sandbox a customer's balance\nstarts at zero, so their first card is `active` and declines every authorization for\n`Insufficient balance` until you load it with `POST /cards/{card_id}/simulate/funding`.\nIn production the card spends the USDC in the\ncardholder's OZAV-managed wallet, so the customer needs one: create them with\n`custody_model: managed` (it needs the `custody` product). Issuing a card for a\ncustomer without the wallet returns `422 CUSTOMER_WALLET_REQUIRED`. There is no separate card load: a deposit reaches the\ncard automatically, usually within minutes, and USDT or other assets do not fund the card.\n"
          },
          "last4": {
            "type": [
              "string",
              "null"
            ],
            "description": "Last four digits of the card number, for display. `null` until the issuer has provisioned the card. The full number is never returned - use a display session."
          },
          "exp_month": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Expiry month, 1-12. `null` when the issuer does not return it.\n\nDo not assume this fills in once the card is provisioned. Some issuers\nnever return expiry to a non-PCI-DSS-Level-1 partner at all: the\ncardholder sees it only inside the display session, and the value never\nreaches this API.\n\nWhen it is `null`, the display session is where the expiry lives - it\ncarries the card number, the CVV and the expiry together. So `null` means\n\"open a display session\", not \"the card is not ready\", and a card with a\n`null` expiry is fully usable. Never derive an expiry of your own.\n"
          },
          "exp_year": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Four-digit expiry year. `null` when the issuer does not return it - the\nsame rule as `exp_month`, and the two are always `null` together.\n"
          },
          "brand_name": {
            "type": "string",
            "description": "The name printed on the card and shown to the cardholder. Defaults to `OZAV`."
          },
          "spending_controls": {
            "$ref": "#/components/schemas/CardSpendingControls"
          },
          "replacement_for": {
            "type": "string",
            "description": "When this card replaces another, the `ozav_card_` id of the one it replaced. Absent on a card that is not a replacement."
          },
          "replacement_reason": {
            "type": "string",
            "enum": [
              "damaged",
              "expired",
              "lost",
              "stolen"
            ],
            "description": "Why the replaced card was reissued. Absent on a card that is not a replacement. Present only alongside `replacement_for`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the card record was created (RFC 3339, UTC). Not when it became usable - that is `activated_at`."
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the card first became `active`. `null` while still `pending`. In the sandbox this is stamped at creation, so a test card skips the pending window."
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When `/close` cancelled the card. `null` on a card that was never closed. Being terminal, this never returns to `null`."
          }
        }
      },
      "CardDisplaySession": {
        "type": "object",
        "required": [
          "id",
          "object",
          "card_id",
          "display_url",
          "expires_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque display-session id, prefixed `ozav_cds_`."
          },
          "object": {
            "type": "string",
            "const": "card_display_session",
            "description": "Resource type discriminator; always the literal `card_display_session`."
          },
          "card_id": {
            "type": "string",
            "description": "The `ozav_card_` id whose sensitive data this session reveals."
          },
          "display_url": {
            "type": "string",
            "format": "uri",
            "description": "OZAV-hosted short-lived display URL."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "When `display_url` stops working (RFC 3339, UTC). Sessions are short-lived; mint a new one rather than storing this URL."
          }
        }
      },
      "CardActivityPage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "required": [
              "object",
              "card_id",
              "data",
              "has_more",
              "next_cursor"
            ],
            "properties": {
              "object": {
                "type": "string",
                "const": "list",
                "description": "Resource type discriminator for the page envelope; always `list`. What the items are is in `item_object`."
              },
              "card_id": {
                "type": "string",
                "description": "The `ozav_card_` id this page belongs to; echoed so a page can be attributed without keeping the request around."
              },
              "data": {
                "type": "array",
                "description": "The activity rows. Their shape depends on `item_object` - read that first. Interchange figures and issuer identifiers are never included.",
                "items": {
                  "$ref": "#/components/schemas/CardActivity"
                }
              },
              "item_object": {
                "type": "string",
                "enum": [
                  "card_transaction",
                  "card_authorization"
                ],
                "description": "What the rows in `data` are, and they are not the same thing.\n`card_authorization` - the issuer's hold at purchase time; it can expire or be\nreversed and never settle. `card_transaction` - the settled movement.\nSumming both together double-counts the same purchase.\n"
              }
            }
          }
        ]
      },
      "CardDeclinePage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "required": [
              "object",
              "card_id",
              "data",
              "has_more",
              "next_cursor",
              "item_object"
            ],
            "properties": {
              "object": {
                "type": "string",
                "const": "list",
                "description": "Resource type discriminator for the page envelope; always `list`."
              },
              "card_id": {
                "type": "string",
                "description": "The `ozav_card_` id this page belongs to."
              },
              "data": {
                "type": "array",
                "description": "The refused purchases, newest first.",
                "items": {
                  "$ref": "#/components/schemas/CardDecline"
                }
              },
              "item_object": {
                "type": "string",
                "const": "card_decline",
                "description": "Always `card_decline`."
              }
            }
          }
        ]
      },
      "CardDecline": {
        "type": "object",
        "description": "A purchase OZAV refused. It moved no money and left no hold.",
        "required": [
          "id",
          "object",
          "card_id",
          "amount_usd",
          "decline_code",
          "fraud_category",
          "merchant",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the `card.declined` event that announced this refusal, prefixed `ozav_evt_`."
          },
          "object": {
            "type": "string",
            "const": "card_decline",
            "description": "Resource type discriminator; always `card_decline`."
          },
          "card_id": {
            "type": "string",
            "description": "The `ozav_card_` id of the card."
          },
          "amount_usd": {
            "type": "string",
            "description": "The amount the purchase asked for, in USD, as a decimal string.",
            "example": "30.00"
          },
          "decline_code": {
            "type": "string",
            "enum": [
              "insufficient_balance",
              "card_frozen",
              "card_inactive",
              "account_inactive",
              "spending_limit",
              "mcc_blocked",
              "country_blocked",
              "presence_blocked",
              "fraud_rule",
              "temporarily_unavailable"
            ],
            "description": "Why the purchase was refused. Same codes as the `card.declined` event."
          },
          "fraud_category": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "velocity",
              "transaction_amount",
              "period_spend",
              "merchant_category",
              "merchant_country",
              "time_of_day",
              "other",
              null
            ],
            "description": "The kind of fraud rule that refused the purchase. Set only when `decline_code` is `fraud_rule`."
          },
          "merchant": {
            "type": "object",
            "required": [
              "name",
              "category_code",
              "country"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The merchant name as the card network sent it."
              },
              "category_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The merchant category code (MCC)."
              },
              "country": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The merchant country, ISO 3166-1 alpha-2."
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the refusal was recorded (RFC 3339, UTC)."
          }
        }
      },
      "CardActivity": {
        "type": "object",
        "description": "One row of card activity. `item_object` on the page says whether these are settled transactions or authorisation holds - the shape is the same, the meaning is not.\n\nInterchange figures and issuer identifiers are never included.",
        "required": [
          "id",
          "object",
          "card_id",
          "direction",
          "amount",
          "currency",
          "status",
          "merchant",
          "created_at",
          "settled_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque activity id, prefixed `ozav_ctxn_`.",
            "example": "ozav_ctxn_aa11bb22cc33dd44ee55ff6677889900"
          },
          "object": {
            "type": "string",
            "enum": [
              "card_transaction",
              "card_authorization"
            ],
            "description": "Matches the page's `item_object`."
          },
          "card_id": {
            "type": "string",
            "description": "The `ozav_card_` id this activity belongs to."
          },
          "direction": {
            "type": "string",
            "enum": [
              "debit",
              "credit"
            ],
            "description": "`debit` for a purchase, `credit` for a refund. The amount is always positive and the direction says what it did - a signed amount is easy to mis-render and easy to sum wrongly."
          },
          "amount": {
            "type": "string",
            "description": "The amount charged to the card, in the card's currency, always positive.",
            "example": "50.00"
          },
          "currency": {
            "type": "string",
            "description": "The card's own currency. Always `USD` today: the card programme is USD-funded, so this is what the cardholder is actually charged. It is not the merchant's currency - see `presentment`.",
            "example": "USD"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "authorized",
              "settled",
              "declined",
              "reversed"
            ]
          },
          "merchant": {
            "type": "object",
            "required": [
              "name",
              "category_code",
              "country"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Cafe Lisboa"
              },
              "category_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "MCC.",
                "example": "5812"
              },
              "country": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Where the merchant is registered. Not a reliable test for an international purchase - a merchant can be registered in one country and charge in another currency. Use `presentment`.",
                "example": "PT"
              }
            }
          },
          "presentment": {
            "type": "object",
            "description": "What the purchase cost in the merchant's currency, when the issuer reports it.\n\n**This is how you identify an international transaction:** `presentment.currency !== currency`. Comparing `merchant.country` instead answers a different question and gets it wrong for a merchant registered in one country that bills in another.\n\n**The key is absent when the issuer did not report it** - it is never filled in with the card currency. Absent means \"we were not told\", which has to stay distinguishable from a genuinely domestic purchase. So treat a missing `presentment` as unknown, not as domestic.\n\n`amount` is already scaled for the currency, so a zero-decimal currency comes back without decimals: JPY 5000 is `\"5000\"`, not `\"50.00\"`.",
            "required": [
              "amount",
              "currency"
            ],
            "properties": {
              "amount": {
                "type": "string",
                "example": "5000"
              },
              "currency": {
                "type": "string",
                "description": "ISO 4217 code of the merchant's currency.",
                "example": "JPY"
              }
            }
          },
          "domestic": {
            "type": "boolean",
            "description": "**Deprecated and normally absent - do not build on it.** It has no writer today, so it is omitted from every row the current pipeline produces. Use `presentment` to tell a domestic purchase from an international one."
          },
          "client_surcharge": {
            "$ref": "#/components/schemas/ClientSurcharge"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "settled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null while an authorisation has not settled."
          }
        }
      },
      "ClientSurcharge": {
        "type": "object",
        "description": "Your surcharge on this purchase, charged to the cardholder together with the purchase. It is not part of `amount`. Absent when you had no active surcharge rules - absent is not zero: a rule that rated 0 appears with `amount: \"0.00\"`. On a refund (`direction: credit`) it is the surcharge returned to the cardholder with the refund, at the purchase's rate - it reduces what you are credited, so do not add it to your earnings.",
        "required": [
          "amount",
          "currency",
          "rate_bps",
          "basis",
          "rule_version",
          "rule_index"
        ],
        "properties": {
          "amount": {
            "type": "string",
            "description": "In the card's currency, two decimals.",
            "example": "9.00"
          },
          "currency": {
            "type": "string",
            "example": "USD"
          },
          "rate_bps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2000,
            "example": 900
          },
          "basis": {
            "type": "string",
            "enum": [
              "rule",
              "default",
              "presentment_missing"
            ],
            "description": "What decided the rate: a rule (`rule_index`), `default_rate_bps`, or `presentment_missing_rate_bps` because the purchase currency was not reported."
          },
          "rule_version": {
            "type": "integer",
            "minimum": 1,
            "description": "The published version that decided the rate."
          },
          "rule_index": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Zero-based position of the matching rule; null unless `basis` is `rule`."
          }
        }
      },
      "CardSurchargeCondition": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "description": "Every condition present must match. `presentment_currency_in` and `presentment_currency_not_in` are mutually exclusive, and neither matches a purchase whose currency was not reported.",
        "properties": {
          "presentment_currency_in": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z]{3}$"
            }
          },
          "presentment_currency_not_in": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z]{3}$"
            }
          },
          "merchant_name_contains_any": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "\\S",
              "description": "Trimmed; blank is refused."
            }
          },
          "merchant_name_starts_with_any": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "\\S",
              "description": "Trimmed; blank is refused."
            }
          },
          "mcc_in": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "pattern": "^[0-9]{4}$"
            }
          }
        }
      },
      "CardSurchargeRule": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "when",
          "rate_bps"
        ],
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "when": {
            "$ref": "#/components/schemas/CardSurchargeCondition"
          },
          "rate_bps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2000,
            "description": "Integer basis points; 2000 is 20%."
          }
        }
      },
      "CardSurchargeRulesPut": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "enabled",
          "rules",
          "default_rate_bps",
          "presentment_missing_rate_bps"
        ],
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "false publishes a version that charges nothing."
          },
          "rules": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/CardSurchargeRule"
            }
          },
          "default_rate_bps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2000
          },
          "presentment_missing_rate_bps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2000,
            "description": "Required: the rate when the issuer did not report the purchase currency. OZAV never picks one of your rates for you."
          }
        }
      },
      "CardSurchargeRules": {
        "type": "object",
        "required": [
          "object",
          "environment",
          "version",
          "enabled",
          "rules",
          "default_rate_bps",
          "presentment_missing_rate_bps",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "card_surcharge_rules"
          },
          "environment": {
            "type": "string",
            "enum": [
              "test",
              "live"
            ]
          },
          "version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "null = never published."
          },
          "enabled": {
            "type": "boolean"
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CardSurchargeRule"
            }
          },
          "default_rate_bps": {
            "type": "integer"
          },
          "presentment_missing_rate_bps": {
            "type": "integer"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Wallet": {
        "type": "object",
        "description": "Balances and custody model for a customer's holdings. A 1:1 projection - a customer has at most one wallet.",
        "required": [
          "id",
          "object",
          "custody_model",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque wallet identifier, prefixed `ozav_wal_`."
          },
          "object": {
            "type": "string",
            "const": "wallet",
            "description": "Resource type discriminator; always the literal `wallet`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the wallet's owning customer. Absent for a wallet registered with `owner_ref`."
          },
          "owner_ref": {
            "type": "string",
            "description": "Your own reference for the wallet's owner. Present only for a wallet registered with `owner_ref` (no OZAV customer)."
          },
          "network": {
            "type": "string",
            "description": "The network of a wallet registered with `owner_ref`."
          },
          "address": {
            "type": "string",
            "description": "The address of a wallet registered with `owner_ref`, as your user registered it."
          },
          "custody_model": {
            "type": "string",
            "enum": [
              "managed",
              "self_custody"
            ],
            "description": "`managed` wallets are held under OZAV-side custody arrangements;\n`self_custody` wallets are controlled by the client or their\nend-user, and OZAV only builds unsigned transactions.\n"
          },
          "status": {
            "type": "string",
            "description": "Whether the wallet is usable. A wallet is `active` only while its\nowning customer account is active; any other account state\n(pending, suspended, closed) projects to `inactive`.\n",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the wallet was created (RFC 3339 / ISO 8601, UTC)."
          }
        }
      },
      "Balance": {
        "type": "object",
        "description": "A wallet balance for one currency (and network, where applicable).",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/DecimalAmount"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          }
        }
      },
      "OnchainBalance": {
        "type": "object",
        "description": "The balance held at a deposit address on one network, for one currency. Not the ledger balance (see `Balance`): no in-flight outbound is subtracted, finality is not awaited, and an uncredited inbound deposit is included. Networks with a shared deposit address never appear here - their on-chain balance belongs to every customer at once.\n",
        "required": [
          "object",
          "network",
          "currency",
          "address",
          "amount"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "onchain_balance"
            ],
            "description": "Resource type discriminator; always the literal `onchain_balance`. It marks this as the chain reading, not the ledger `Balance`."
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "address": {
            "type": "string",
            "description": "The address this balance was read from - the same address `/deposit-addresses` returns for this network.\n"
          },
          "amount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null` means the balance could not be read, never that it is zero. A zero here is a measured zero. Treat `null` as \"unknown\" and retry; treating it as `0` would report an empty wallet that may be full.\n"
          },
          "unreadable_reason": {
            "type": "string",
            "description": "Present only when `amount` is `null`. Why the read failed."
          }
        }
      },
      "WalletAddress": {
        "type": "object",
        "description": "One entry of a managed wallet's address book - the destinations that leave without human review (`allowed`), plus the requests OZAV has not decided (`pending_review`) or refused (`rejected`). The book is the allowlist that the custody plane applies, with its labels.\n",
        "required": [
          "object",
          "network",
          "address",
          "label",
          "status",
          "added_at",
          "requested_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "wallet_address"
            ]
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "address": {
            "type": "string",
            "description": "As the custody plane matches it (EVM lower-cased)."
          },
          "label": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "allowed",
              "pending_review",
              "rejected"
            ]
          },
          "added_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When it entered the allowlist; `null` until approved."
          },
          "requested_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Approval": {
        "type": "object",
        "description": "An approval of your organization's quorum, as `GET /approvals` lists it. OZAV sets the rules of managed-custody wallets, and no operation waits on approver keys, so no approval is opened.\n",
        "required": [
          "id",
          "object",
          "kind",
          "status",
          "resource",
          "wallet_id",
          "required_approvals",
          "approvals_received",
          "summary",
          "decisions",
          "requested_by_api_key_id",
          "failure_reason",
          "expires_at",
          "created_at",
          "decided_at",
          "executed_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque approval identifier, prefixed `ozav_apr_`."
          },
          "object": {
            "type": "string",
            "enum": [
              "approval"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "wallet_transfer",
              "wallet_policy",
              "wallet_unfreeze"
            ],
            "description": "What the approval was about."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "rejected",
              "expired",
              "executed",
              "failed"
            ],
            "description": "Where the approval ended."
          },
          "resource": {
            "type": "object",
            "required": [
              "type",
              "id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "transfer",
                  "wallet_transfer",
                  "wallet_policy",
                  "wallet_freeze"
                ]
              },
              "id": {
                "type": "string",
                "description": "The `ozav_tr_`, `ozav_wtr_` or `ozav_wal_` id the approval is about."
              }
            }
          },
          "wallet_id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "required_approvals": {
            "type": "integer"
          },
          "approvals_received": {
            "type": "integer"
          },
          "summary": {
            "type": "object",
            "description": "What is being approved, in brief (amount, currency, destination, or the requested quorum). Never the frozen request.",
            "additionalProperties": true
          },
          "decisions": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "api_key_id",
                "decision",
                "reason",
                "decided_at"
              ],
              "properties": {
                "api_key_id": {
                  "type": "string"
                },
                "decision": {
                  "type": "string",
                  "enum": [
                    "approve",
                    "reject"
                  ]
                },
                "reason": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "decided_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "requested_by_api_key_id": {
            "type": "string",
            "description": "The key that created the operation. It cannot approve or reject it."
          },
          "failure_reason": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "decided_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "executed_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "WalletPolicy": {
        "type": "object",
        "description": "What OZAV applies before signing a send from a managed wallet, and the limit requests still being decided. Read `limits.source` first: it says whether the numbers are a per-wallet signed record, your organization's defaults, or nothing at all (everything reviewed).\n",
        "required": [
          "object",
          "wallet_id",
          "destinations",
          "limits",
          "approvals",
          "pending_changes",
          "updated_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "wallet_policy"
            ],
            "description": "Resource type discriminator; always the literal `wallet_policy`."
          },
          "wallet_id": {
            "type": "string",
            "description": "The `ozav_wal_` id this policy belongs to."
          },
          "destinations": {
            "type": "object",
            "required": [
              "mode",
              "allowed"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "allowlist",
                  "risk"
                ],
                "description": "Your organization's destination setting, shown as configured. The rule OZAV applies is the same in both modes: up to the automatic amount a clean send to any address leaves on its own; above it only addresses in `allowed` (or OZAV's own) receive, and any other is refused (`422 DESTINATION_NOT_ALLOWLISTED`).\n"
              },
              "allowed": {
                "type": "array",
                "description": "Destinations that leave without human review, per network.",
                "items": {
                  "$ref": "#/components/schemas/WalletPolicyAllowedDestination"
                }
              }
            }
          },
          "limits": {
            "type": "object",
            "required": [
              "per_transaction_usd",
              "daily_usd",
              "auto_approve_below_usd",
              "source",
              "valid_until"
            ],
            "properties": {
              "per_transaction_usd": {
                "oneOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Ceiling for one send, in USD. `null` when `source` is `unconfigured`."
              },
              "daily_usd": {
                "oneOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Ceiling for the rolling 24-hour window, in USD."
              },
              "auto_approve_below_usd": {
                "oneOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Up to and including this amount a clean send to a destination not in `allowed` leaves on its own; above it the send is refused (`422 DESTINATION_NOT_ALLOWLISTED`). `0` means a send to any unlisted destination is refused. When `source` is `wallet` and more than one record is active, the smallest applies, and never above the per-send limit; otherwise the house ceiling (USD 100,000) applies.\n"
              },
              "source": {
                "type": "string",
                "enum": [
                  "wallet",
                  "organization",
                  "unconfigured"
                ],
                "description": "`wallet`: a signed per-wallet record is in force (with more than one active, the smallest of each limit applies). `organization`: no per-wallet record; the numbers shown are your organization's defaults, and sends are checked against the house ceiling (USD 100,000) until a record is signed. `unconfigured`: neither exists; the house ceiling applies.\n"
              },
              "valid_until": {
                "oneOf": [
                  {
                    "type": "string",
                    "format": "date-time"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "When the per-wallet record expires; `null` unless `source` is `wallet`."
              }
            }
          },
          "approvals": {
            "type": "object",
            "required": [
              "mode"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "ozav_review"
                ],
                "description": "Always `ozav_review`. OZAV's compliance desk decides what falls outside the limits."
              }
            }
          },
          "cosigner": {
            "type": "object",
            "description": "Always `mode: none`. OZAV sets the rules of managed-custody wallets.",
            "required": [
              "mode"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "none"
                ]
              }
            }
          },
          "delay": {
            "type": "object",
            "description": "Always `mode: none`. No send from a managed-custody wallet waits on a delay.",
            "required": [
              "mode"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "none"
                ]
              }
            }
          },
          "pending_changes": {
            "type": "array",
            "description": "Requests not yet in force, newest first: awaiting review, approved (limits only - in force once the signed record is issued), or rejected.\n",
            "items": {
              "$ref": "#/components/schemas/WalletPolicyPendingChange"
            }
          },
          "updated_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the policy in force last changed; `null` when nothing is configured yet."
          }
        }
      },
      "WalletPolicyAllowedDestination": {
        "type": "object",
        "required": [
          "network",
          "address",
          "added_at"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "The label you gave the destination (\"cold vault\", \"treasury exchange\"). Display only."
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "address": {
            "type": "string",
            "description": "The destination address, as the custody plane matches it (EVM lower-cased)."
          },
          "added_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WalletPolicyPendingChange": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "status",
          "requested_at",
          "decided_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The change id, echoed in `wallet_policy.updated` when it is decided."
          },
          "kind": {
            "type": "string",
            "enum": [
              "destination.add",
              "limits.update"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_review",
              "approved",
              "rejected"
            ],
            "description": "`pending_review`: awaiting OZAV. `approved`: accepted, not yet in force (limits only - wait for the signed record; a destination is in `allowed` the moment it is approved, so it never shows as `approved` here). `rejected`: refused; `reason` may say why.\n"
          },
          "requested_at": {
            "type": "string",
            "format": "date-time"
          },
          "decided_at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "address": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "per_transaction_usd": {
            "type": "number"
          },
          "daily_usd": {
            "type": "number"
          },
          "auto_approve_below_usd": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "WalletPolicyChangeRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "limits"
        ],
        "description": "A limit request. `destinations`, `approvals`, `delay` and `cosigner` answer `422 WALLET_RULE_SET_BY_OZAV`.",
        "properties": {
          "limits": {
            "type": "object",
            "additionalProperties": false,
            "description": "Fields you omit inherit the values in force. When nothing is in force yet, `per_transaction_usd` and `daily_usd` are both required.\n",
            "properties": {
              "per_transaction_usd": {
                "type": "number",
                "minimum": 0,
                "description": "Ceiling for one send, in USD."
              },
              "daily_usd": {
                "type": "number",
                "minimum": 0,
                "description": "Ceiling for the rolling 24-hour window, in USD. Must be at least `per_transaction_usd`."
              },
              "auto_approve_below_usd": {
                "oneOf": [
                  {
                    "type": "number",
                    "minimum": 0
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Below this a clean send to an unlisted destination leaves without review. Must not exceed `per_transaction_usd`. `null` means 0: every unlisted destination is reviewed.\n"
              }
            }
          }
        }
      },
      "DepositAddress": {
        "type": "object",
        "description": "An on-chain address that credits this wallet. Read `memo_required` before building a deposit instruction: on networks that attribute by memo, every customer shares one account and the memo is the only thing that routes funds to the right one. A deposit sent without it cannot be attributed.\n",
        "required": [
          "object",
          "network",
          "address",
          "memo_required"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "deposit_address"
            ],
            "description": "Resource type discriminator; always the literal `deposit_address`."
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "address": {
            "type": "string",
            "description": "The on-chain address to send to."
          },
          "memo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present and non-null exactly when `memo_required` is true. Send it with the deposit, unchanged. Null on address-per-user networks, where it must not be sent.\n"
          },
          "memo_required": {
            "type": "boolean",
            "description": "Whether the deposit is attributed by memo rather than by address. Do not infer this from `memo` being non-null - read this field.\n"
          },
          "memo_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "text",
              null
            ],
            "description": "How the memo must be sent. `text` means a Stellar MEMO_TEXT. Null where no memo applies.\n\n**Send it as a text memo, never as a numeric one.** Most wallets and exchanges offer a numeric memo/tag field (Stellar MEMO_ID) by default, and our memo is a base64url string, so it will be rejected as invalid there - or, worse, accepted into the numeric field and sent as a different memo entirely. Attribution is an exact string match: a numeric memo never matches, and the deposit arrives unattributed.\n"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the address was assigned to this wallet (RFC 3339, UTC). `null` on an address that predates the record being kept - it does not mean the address is new."
          }
        }
      },
      "UnsignedTransaction": {
        "type": "object",
        "description": "An unsigned EVM transaction for a self-custody wallet to sign and broadcast. OZAV never signs it.",
        "required": [
          "to",
          "data",
          "value",
          "chain_id"
        ],
        "properties": {
          "to": {
            "type": "string",
            "description": "The destination contract or account address (0x-prefixed hex)."
          },
          "data": {
            "type": "string",
            "description": "ABI-encoded calldata for the transaction (0x-prefixed hex; empty for a plain value transfer)."
          },
          "value": {
            "type": "string",
            "description": "Native-token amount to send, in wei, as a decimal string (\"0\" for token transfers)."
          },
          "chain_id": {
            "type": "integer",
            "description": "EIP-155 chain id identifying the target network."
          }
        }
      },
      "TransactionIntent": {
        "type": "object",
        "description": "The result of building an unsigned transaction for a self-custody wallet. Ephemeral - not persisted and not re-fetchable.",
        "required": [
          "id",
          "object",
          "unsigned_transaction",
          "expires_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque transaction-intent identifier, prefixed `ozav_txi_`."
          },
          "object": {
            "type": "string",
            "const": "transaction_intent",
            "description": "Resource type discriminator; always the literal `transaction_intent`."
          },
          "wallet_id": {
            "type": "string",
            "description": "The `ozav_wal_` id of the self-custody wallet the transaction is built for."
          },
          "intent": {
            "type": "string",
            "description": "The kind of transaction that was built:\n- `withdraw` - send funds out of the wallet to an external destination.\n- `transfer` - move funds from the wallet to another address.\n- `deposit` - inbound; not buildable here (a deposit is signed by the external payer).\n",
            "enum": [
              "deposit",
              "withdraw",
              "transfer"
            ]
          },
          "unsigned_transaction": {
            "$ref": "#/components/schemas/UnsignedTransaction"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Validity horizon of the unsigned transaction, after which nonce/gas may be stale (RFC 3339 / ISO 8601, UTC)."
          }
        }
      },
      "ScreeningSubject": {
        "description": "The screened subject. Provide exactly one of `customer_id`, `person`,\n`business`, or `wallet_address`. Inline subjects (`person` / `business`)\nenable standalone screening with no customer record.\n",
        "oneOf": [
          {
            "type": "object",
            "description": "Screen an existing customer by id.",
            "required": [
              "customer_id"
            ],
            "properties": {
              "customer_id": {
                "type": "string",
                "description": "The `ozav_cus_` id of an existing customer to screen."
              }
            }
          },
          {
            "type": "object",
            "description": "Screen a person inline (no customer record required).",
            "required": [
              "person"
            ],
            "properties": {
              "person": {
                "type": "object",
                "description": "The natural person to screen. Screened as given - no customer record is created and nothing is stored against a customer.",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Full name of the person to screen."
                  },
                  "date_of_birth": {
                    "type": "string",
                    "format": "date",
                    "description": "Date of birth (ISO 8601 date) - helps disambiguate matches."
                  },
                  "country": {
                    "type": "string",
                    "description": "Country of the person as an ISO 3166-1 alpha-2 code.",
                    "pattern": "^[A-Z]{2}$"
                  },
                  "email": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 254,
                    "description": "Optional contact email. Evaluated for contact signals (disposable mailbox; for businesses also non-existent domain, no mail server, domain age) when contact signals are enabled on the OZAV platform - a platform-wide setting, see `contact_evaluated`. Never stored or echoed back - only the resulting signal is kept, and for a person the email domain is quoted only when it is a public disposable-mail domain."
                  },
                  "phone": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 40,
                    "description": "Optional contact phone (E.164 preferred; without `+`, send `country` so the number can be read - otherwise it is not evaluated). Evaluated for contact signals (invalid number, virtual line, country mismatch) when contact signals are enabled on the OZAV platform - a platform-wide setting. Never stored or echoed back."
                  }
                }
              }
            }
          },
          {
            "type": "object",
            "description": "Screen a business inline (no customer record required).",
            "required": [
              "business"
            ],
            "properties": {
              "business": {
                "type": "object",
                "description": "The legal entity to screen. Screened as given - no customer record is created and nothing is stored against a customer.",
                "required": [
                  "legal_name"
                ],
                "properties": {
                  "legal_name": {
                    "type": "string",
                    "description": "Registered legal name of the business to screen."
                  },
                  "country": {
                    "type": "string",
                    "description": "Country of registration as an ISO 3166-1 alpha-2 code.",
                    "pattern": "^[A-Z]{2}$"
                  },
                  "email": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 254,
                    "description": "Optional contact email. Evaluated for contact signals (disposable mailbox; for businesses also non-existent domain, no mail server, domain age) when contact signals are enabled on the OZAV platform - a platform-wide setting, see `contact_evaluated`. Never stored or echoed back - only the resulting signal is kept, and for a person the email domain is quoted only when it is a public disposable-mail domain."
                  },
                  "phone": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 40,
                    "description": "Optional contact phone (E.164 preferred; without `+`, send `country` so the number can be read - otherwise it is not evaluated). Evaluated for contact signals (invalid number, virtual line, country mismatch) when contact signals are enabled on the OZAV platform - a platform-wide setting. Never stored or echoed back."
                  }
                }
              }
            }
          },
          {
            "type": "object",
            "required": [
              "wallet_address"
            ],
            "description": "Wallet-address screening is not available: this variant returns 422 FEATURE_NOT_AVAILABLE.\n",
            "properties": {
              "wallet_address": {
                "type": "object",
                "description": "The blockchain address to screen. Not available - sending this variant returns 422 `FEATURE_NOT_AVAILABLE`, it does not screen and return `clear`.",
                "required": [
                  "address",
                  "network"
                ],
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "The blockchain address to screen."
                  },
                  "network": {
                    "$ref": "#/components/schemas/Network"
                  }
                }
              }
            }
          }
        ]
      },
      "ScreeningMatch": {
        "type": "object",
        "description": "A single match against a designated list.",
        "required": [
          "list_source",
          "matched_name",
          "strength"
        ],
        "properties": {
          "list_source": {
            "type": "string",
            "description": "Which designated-list source produced the match:\n- `un` - United Nations Security Council consolidated list.\n- `ofac` - US OFAC (SDN and related lists).\n- `eu` - European Union consolidated sanctions list.\n- `uk` - UK OFSI consolidated list.\n- `national` - a national designation list.\n",
            "enum": [
              "un",
              "ofac",
              "eu",
              "uk",
              "national"
            ]
          },
          "matched_name": {
            "type": "string",
            "description": "The name on the list that matched the screened subject."
          },
          "strength": {
            "type": "string",
            "description": "How strong the name match is:\n- `exact` - the names match exactly.\n- `alias` - the subject matched a known alias of a listed entity.\n- `fuzzy` - a weaker, token-level match (surfaced for review, never auto-cleared).\n",
            "enum": [
              "exact",
              "alias",
              "fuzzy"
            ]
          },
          "list_entry_ref": {
            "type": "string",
            "description": "The list's own reference id for the matched entry (e.g. an OFAC SDN number), for audit."
          },
          "id": {
            "type": "string",
            "description": "The hit id (`hit_...`). It is the hit's fingerprint - list, entry, strength and listed name - so the same subject hitting the same entry again carries the same id. Use it with `PATCH /screenings/{screening_id}/hits/{hit_id}`."
          },
          "status": {
            "type": "string",
            "description": "The decision on this hit. Reused by later screenings of the same subject when it is\nidentifiable (`customer_id`, or a person with `date_of_birth`):\n- `open` - not decided.\n- `false_positive` - decided as not being the subject; no longer escalates.\n- `confirmed` - decided as the subject. A confirmed sanctions hit makes\n  `suggested_decision` `decline`.\nA changed list entry (other strength or listed name) is a new hit and starts `open`.\n",
            "enum": [
              "open",
              "false_positive",
              "confirmed"
            ]
          },
          "decided_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who decided: `you` (your organization, via the API) or `ozav` (OZAV compliance, shown\nuntil you decide; your decision prevails). `null` while `open`.\n",
            "enum": [
              "you",
              "ozav",
              null
            ]
          }
        }
      },
      "ScreeningHitDecisionRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "decision",
          "reason"
        ],
        "properties": {
          "decision": {
            "type": "string",
            "description": "- `false_positive` - the hit is not the subject.\n- `confirmed` - the hit is the subject.\n",
            "enum": [
              "false_positive",
              "confirmed"
            ]
          },
          "reason": {
            "type": "string",
            "minLength": 10,
            "maxLength": 500,
            "description": "Why - kept for audit (at least 10 characters, no NUL). Do not put personal data here."
          }
        }
      },
      "ScreeningHit": {
        "type": "object",
        "required": [
          "id",
          "object",
          "screening_id",
          "category",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The hit id (`hit_...`)."
          },
          "object": {
            "type": "string",
            "const": "screening_hit",
            "description": "Always the literal `screening_hit`."
          },
          "screening_id": {
            "type": "string",
            "description": "The screening the decision was made on (`ozav_scr_...`)."
          },
          "category": {
            "type": "string",
            "description": "- `sanctions_list` - a sanctions-list hit.\n- `pep_list` - a PEP-register hit.\n",
            "enum": [
              "sanctions_list",
              "pep_list"
            ]
          },
          "status": {
            "type": "string",
            "description": "The effective decision now (yours prevails over OZAV's).",
            "enum": [
              "open",
              "false_positive",
              "confirmed"
            ]
          },
          "decided_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "`you` or `ozav`.",
            "enum": [
              "you",
              "ozav",
              null
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "The reason of the effective decision."
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the effective decision was made."
          }
        }
      },
      "ListEntryCreateRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "list",
          "type",
          "value"
        ],
        "properties": {
          "list": {
            "type": "string",
            "description": "- `block` - subjects matching this entry escalate (`review`, or `decline` with `action`).\n  A block match always wins over an allow match.\n- `allow` - suppresses only contact signals for a matching subject - never a block-list,\n  sanctions or PEP match. A free or disposable email domain cannot be allow-listed.\n",
            "enum": [
              "block",
              "allow"
            ]
          },
          "type": {
            "type": "string",
            "description": "What the value is (no faces, no wallet addresses), and which screenings it can match:\n\n| type | inline subject (`person`/`business`) | `customer_id` subject |\n|---|---|---|\n| `name` | the name sent | the stored name |\n| `document` | never (inline screenings carry no document) | the stored tax id |\n| `email` | the `email` sent | the stored email |\n| `email_domain` | the domain of the `email` sent | the domain of the stored email |\n",
            "enum": [
              "name",
              "document",
              "email",
              "email_domain"
            ]
          },
          "value": {
            "type": "string",
            "minLength": 1,
            "maxLength": 320,
            "description": "The value. Stored only as a keyed hash plus a masked hint - it is never returned in full."
          },
          "action": {
            "type": "string",
            "description": "Block list only (default `review`):\n- `review` - a match makes `suggested_decision` `review`.\n- `decline` - a match makes `suggested_decision` `decline`.\n",
            "enum": [
              "review",
              "decline"
            ]
          }
        }
      },
      "DecisionCreateRequest": {
        "description": "What to decide. `type` selects the shape of `input`.",
        "oneOf": [
          {
            "type": "object",
            "required": [
              "type",
              "input"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "const": "name.match",
                "description": "Is this name on a sanctions list?"
              },
              "input": {
                "type": "object",
                "required": [
                  "name"
                ],
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Full name of the person or business."
                  },
                  "date_of_birth": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD. Without it a full-name match is `REVIEW`, never `DENY`."
                  }
                }
              }
            }
          },
          {
            "type": "object",
            "required": [
              "type",
              "input"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "const": "destination.ownership",
                "description": "Is this destination the customer's own?"
              },
              "input": {
                "type": "object",
                "required": [
                  "rail",
                  "destination_tax_id",
                  "customer_tax_id"
                ],
                "additionalProperties": false,
                "properties": {
                  "rail": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "description": "The payout rail, e.g. `pix`. The same-owner rule applies to Pix only."
                  },
                  "destination_tax_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "description": "CPF/CNPJ of the destination account holder."
                  },
                  "customer_tax_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "description": "CPF/CNPJ of your customer."
                  }
                }
              }
            }
          },
          {
            "type": "object",
            "required": [
              "type",
              "input"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "const": "wallet.risk",
                "description": "Is this address on a sanctions list?"
              },
              "input": {
                "type": "object",
                "required": [
                  "address"
                ],
                "additionalProperties": false,
                "properties": {
                  "address": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 120,
                    "description": "The wallet address."
                  },
                  "chain": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Optional network label, recorded as given."
                  }
                }
              }
            }
          },
          {
            "type": "object",
            "required": [
              "type",
              "input"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "const": "transaction.outbound",
                "description": "One outgoing payment: the other party, wallet and ownership together."
              },
              "input": {
                "type": "object",
                "required": [
                  "destination"
                ],
                "additionalProperties": false,
                "properties": {
                  "amount_usd": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "Optional. Recorded for context; the verdict does not depend on it."
                  },
                  "counterparty": {
                    "type": "object",
                    "required": [
                      "name"
                    ],
                    "additionalProperties": false,
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200,
                        "description": "Full name of the other party to the payment."
                      },
                      "date_of_birth": {
                        "type": "string",
                        "format": "date",
                        "description": "YYYY-MM-DD."
                      }
                    }
                  },
                  "destination": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                      "wallet_address": {
                        "type": "string",
                        "minLength": 10,
                        "maxLength": 120,
                        "description": "Destination wallet address."
                      },
                      "rail": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 40,
                        "description": "Payout rail. `rail`, `destination_tax_id` and `customer_tax_id` go together: with them, ownership is checked."
                      },
                      "destination_tax_id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 40,
                        "description": "CPF/CNPJ of the destination account holder."
                      },
                      "customer_tax_id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 40,
                        "description": "CPF/CNPJ of your customer."
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "DecisionOutcomeRequest": {
        "type": "object",
        "required": [
          "outcome"
        ],
        "additionalProperties": false,
        "properties": {
          "outcome": {
            "type": "string",
            "description": "- `confirmed_risk` - the risk was real.\n- `false_positive` - the flag was wrong.\n- `completed` - the operation went ahead and completed normally.\n- `reversed` - the operation was reversed or refunded.\n- `chargeback` - the operation ended in a chargeback.\n",
            "enum": [
              "confirmed_risk",
              "false_positive",
              "completed",
              "reversed",
              "chargeback"
            ]
          },
          "note": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "Optional free text."
          }
        }
      },
      "AgentPlatformRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The platform's name, as companies will see it."
          },
          "first_party": {
            "type": "boolean",
            "description": "Omit it, or send false, for a platform that runs agents for other companies. Set it to run agents that spend your own organization's money. A first-party platform receives grants only from the organization that registered it, and a platform that is not first-party can never receive a grant from its own organization. The mode is fixed once the platform has a grant; asking for the other mode on a registered platform answers `409 AGENT_PLATFORM_MODE_CONFLICT`."
          }
        }
      },
      "AgentPlatform": {
        "type": "object",
        "required": [
          "id",
          "object",
          "name",
          "status",
          "first_party",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The platform id (`ozav_apl_...`). Companies use it to authorize you."
          },
          "object": {
            "type": "string",
            "const": "agent_platform",
            "description": "Always the literal `agent_platform`."
          },
          "name": {
            "type": "string",
            "description": "The platform's name."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended"
            ],
            "description": "Whether the platform can act."
          },
          "first_party": {
            "type": "boolean",
            "description": "True when the platform runs agents for its own organization only."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it was registered."
          }
        }
      },
      "AgentMandate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "actions",
          "valid_until"
        ],
        "description": "What a company allows agents to do with its money. A missing list means no restriction on that dimension; an empty list allows nothing.",
        "properties": {
          "actions": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "enum": [
                "x402_payment",
                "mpp_payment",
                "fiat_payout",
                "fiat_funding",
                "payee_registration",
                "prepaid_consumption",
                "yield_application",
                "account_opening"
              ]
            },
            "description": "The kinds of action allowed. `fiat_funding` is a Pix charge that becomes stablecoin in the company's wallet; it is not spending. `payee_registration` lets the agent register a payout destination, and needs `payee_rules`. `prepaid_consumption` lets the agent consume credit the company bought outside OZAV; it moves no money. `yield_application` lets the agent apply the company's custodied balance to a managed yield strategy, and needs `yield_rules`; it is not spending. `account_opening` lets the agent open a named fiat account for the account holder, and needs `account_rules`; it moves no money."
          },
          "per_payment_limit_usd": {
            "type": "number",
            "description": "Largest single payment, in US dollars."
          },
          "daily_limit_usd": {
            "type": "number",
            "description": "Largest total per UTC day, in US dollars."
          },
          "total_budget_usd": {
            "type": "number",
            "description": "Largest total over the life of the mandate, in US dollars."
          },
          "human_approval_above_usd": {
            "type": "number",
            "description": "From this amount up, a person at the company approves."
          },
          "recurring_budget": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "period",
              "amount_usd",
              "timezone"
            ],
            "description": "A budget that resets on a calendar window, for example US$ 200 per month. Spending inside the current window (every agent of the grant together; for an agent's own mandate, that agent identity alone, so the grant's budget is the aggregate cap) cannot pass `amount_usd`. A request counts against the window it was created in, from the moment it is recorded (including one waiting for approval). It sits on top of `daily_limit_usd` and `total_budget_usd`. A payment or payout that would pass it is refused with the reason `above_recurring_budget`. A payout that is returned releases its amount.",
            "properties": {
              "period": {
                "type": "string",
                "enum": [
                  "day",
                  "week",
                  "month"
                ],
                "description": "The window: a local day, a local week (Monday to Sunday) or a calendar month."
              },
              "amount_usd": {
                "type": "number",
                "description": "The most that can be spent inside one window, in US dollars."
              },
              "timezone": {
                "type": "string",
                "maxLength": 64,
                "pattern": "^(UTC|[A-Za-z]+(/[A-Za-z0-9_+-]+)+)$",
                "description": "A time zone name from the tz database, for example `America/Sao_Paulo`. The window resets at local midnight."
              },
              "reset_day": {
                "type": "integer",
                "minimum": 1,
                "maximum": 28,
                "description": "Monthly only: the day of the month the window resets on. Default 1."
              }
            }
          },
          "funding_limits": {
            "type": "object",
            "additionalProperties": false,
            "description": "Limits for `fiat_funding`, separate from spending: money coming in does not use the budget above.",
            "properties": {
              "per_request_usd": {
                "type": "number",
                "description": "Largest single funding, in US dollars of stablecoin delivered."
              },
              "daily_usd": {
                "type": "number",
                "description": "Largest total per UTC day, in US dollars. A charge that expires unpaid does not count."
              }
            }
          },
          "payee_rules": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "holder"
            ],
            "description": "What the agent may register as a payout destination. Required when `actions` has `payee_registration`, and only allowed then. A missing list puts no limit on that dimension; an empty one allows nothing.",
            "properties": {
              "holder": {
                "type": "string",
                "enum": [
                  "self",
                  "third_party_identified"
                ],
                "description": "Whose account: `self` is the account holder's own (the declared tax id must match the document on file); `third_party_identified` also allows another holder, as long as a tax id is declared."
              },
              "currencies": {
                "type": "array",
                "maxItems": 30,
                "items": {
                  "type": "string"
                },
                "description": "Allowed currencies of the destination, for example `BRL`."
              },
              "rails": {
                "type": "array",
                "maxItems": 30,
                "items": {
                  "type": "string"
                },
                "description": "Allowed rails of the destination, for example `pix`."
              },
              "tax_ids": {
                "type": "array",
                "maxItems": 200,
                "items": {
                  "type": "string"
                },
                "description": "Only these tax ids may be declared for the holder. Compared without punctuation and case."
              },
              "max_per_day": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000,
                "description": "Most destinations the agent registers per UTC day. A registration that was refused or failed does not count."
              }
            }
          },
          "yield_rules": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "allowed_strategies"
            ],
            "description": "What the agent may apply to managed yield. Required when `actions` has `yield_application`, and only allowed then. An application stays the company's money (it becomes a yield position in its name), so the spending limits above do not apply to it, only these. The approval threshold (`human_approval_above_usd`) does apply.",
            "properties": {
              "allowed_strategies": {
                "type": "array",
                "minItems": 1,
                "maxItems": 50,
                "items": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9][A-Za-z0-9-]{1,62}$"
                },
                "description": "Strategy ids from `GET /yield/strategies`, for example `base-usdc-prime`. Compared without case. Only strategies in a dollar stablecoin can be applied by an agent."
              },
              "per_application_usd": {
                "type": "number",
                "description": "Largest single application, in US dollars of stablecoin."
              },
              "daily_usd": {
                "type": "number",
                "description": "Largest total per UTC day, in US dollars. An application that was refused or failed before money moved does not count."
              }
            }
          },
          "account_rules": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "max_accounts"
            ],
            "description": "What named fiat accounts the agent may open for the account holder. Required when `actions` has `account_opening`, and only allowed then. A missing list puts no limit on that dimension; an empty one allows nothing. When a list of countries or rails exists, the request must name its country or rail. Corridors are named by currency, country and rail.",
            "properties": {
              "currencies": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "type": "string",
                  "pattern": "^[A-Z]{3}$"
                },
                "description": "Allowed currencies, for example `BRL`."
              },
              "countries": {
                "type": "array",
                "maxItems": 50,
                "items": {
                  "type": "string",
                  "pattern": "^[A-Z]{2}$"
                },
                "description": "Allowed countries of the account, for example `BR`."
              },
              "rails": {
                "type": "array",
                "maxItems": 20,
                "items": {
                  "type": "string",
                  "pattern": "^[a-z_]{2,20}$"
                },
                "description": "Allowed rails, for example `pix` or `fedwire`."
              },
              "max_accounts": {
                "type": "integer",
                "minimum": 1,
                "maximum": 20,
                "description": "Most accounts the agents of the grant open over its life (for an agent's own mandate, that agent alone). An opening that was refused or failed does not count."
              }
            }
          },
          "allowed_payees": {
            "type": "array",
            "description": "Who can be paid.",
            "items": {
              "type": "object",
              "required": [
                "type"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "address",
                    "payout_destination",
                    "agent_created"
                  ],
                  "description": "An on-chain address, a registered payout destination, or `agent_created` for any destination this agent registered itself."
                },
                "network": {
                  "type": "string",
                  "description": "For `address`: the network, as CAIP-2 (for example `eip155:137`)."
                },
                "address": {
                  "type": "string",
                  "description": "For `address`: the address."
                },
                "id": {
                  "type": "string",
                  "description": "For `payout_destination`: its id (`ozav_pd_...`)."
                }
              }
            }
          },
          "allowed_domains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Hosts of the resources that can be paid (`api.example.com`, or `*.example.com` for subdomains)."
          },
          "allowed_networks": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Networks, as CAIP-2."
          },
          "allowed_assets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Assets, for example `USDC`."
          },
          "allowed_corridors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "`SOURCE>TARGET`: `USDC>BRL` for a payout, `BRL>USDC` for a funding."
          },
          "valid_from": {
            "type": "string",
            "format": "date-time",
            "description": "When the mandate starts. Defaults to now."
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "When the mandate ends."
          }
        }
      },
      "AgentGrantRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "platform_id",
          "mandate"
        ],
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this grant: repeating the call with it returns the same grant."
          },
          "platform_id": {
            "type": "string",
            "description": "The platform whose agents receive the mandate (`ozav_apl_...`)."
          },
          "customer_id": {
            "type": "string",
            "description": "One of your customers (`ozav_cus_...`) whose money the agents move. Omit it to grant your organization's own money."
          },
          "mandate": {
            "$ref": "#/components/schemas/AgentMandate"
          }
        }
      },
      "AgentGrant": {
        "type": "object",
        "required": [
          "id",
          "object",
          "status",
          "customer",
          "approved_via",
          "platform",
          "first_party",
          "mandate_version",
          "mandate",
          "approved_at",
          "revoked_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The grant id (`ozav_agr_...`)."
          },
          "object": {
            "type": "string",
            "const": "agent_grant",
            "description": "Always the literal `agent_grant`."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_consent",
              "active",
              "suspended",
              "revoked",
              "expired"
            ],
            "description": "Whether agents can act under it."
          },
          "customer": {
            "type": [
              "string",
              "null"
            ],
            "description": "The customer whose money the grant moves (`ozav_cus_...`). Null when it is the organization's own money, and always null for the platform that received it."
          },
          "approved_via": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "portal_mfa",
              "api_key",
              null
            ],
            "description": "How the grant was approved: in the portal with the authenticator code, or through `POST /agent-grants` with the organization's admin key."
          },
          "first_party": {
            "type": "boolean",
            "description": "True when the organization that gave the grant runs the platform itself (its own agents, its own money). The approval is the same: an administrator confirms it with the authenticator code."
          },
          "platform": {
            "type": [
              "string",
              "null"
            ],
            "description": "The platform it was given to (`ozav_apl_...`)."
          },
          "mandate_version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The mandate version in force."
          },
          "mandate": {
            "description": "The mandate in force; null once revoked.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/AgentMandate"
              },
              {
                "type": "null"
              }
            ]
          },
          "approved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When a person at the company approved it."
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was revoked."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it was created."
          }
        }
      },
      "AgentGrantList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list",
            "description": "Always the literal `list`."
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentGrant"
            },
            "description": "The grants."
          }
        }
      },
      "AgentRegisterRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "grant",
          "label",
          "platform_agent_ref"
        ],
        "properties": {
          "grant": {
            "type": "string",
            "description": "The grant the agent acts under (`ozav_agr_...`)."
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "A name for the agent."
          },
          "platform_agent_ref": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "The agent's id on your platform. Unique per grant."
          },
          "parent_agent": {
            "type": "string",
            "description": "The parent agent (`ozav_agt_...`), for a sub-agent."
          },
          "own_mandate": {
            "$ref": "#/components/schemas/AgentMandate"
          }
        }
      },
      "AgentFundingRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "amount",
          "rail",
          "target"
        ],
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this funding. The same key returns the same recorded charge and never creates a second one."
          },
          "amount": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "amount",
              "currency"
            ],
            "description": "What the company pays, in reais or US dollars. At most two decimal places and greater than zero.",
            "properties": {
              "amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "BRL",
                  "USD"
                ]
              }
            }
          },
          "rail": {
            "type": "string",
            "enum": [
              "pix",
              "wire",
              "fedwire",
              "swift"
            ],
            "description": "How the company pays. Reais only by `pix`; US dollars by `wire`, `fedwire` or `swift`. Any other pairing is `400`. A dollar funding by wire is credited only when the bank statement shows the account holder as the sender (otherwise it is held for review). In the sandbox the bank instructions are simulated."
          },
          "target": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "wallet_id",
              "currency",
              "chain"
            ],
            "description": "The company's managed wallet the stablecoin lands in.",
            "properties": {
              "wallet_id": {
                "type": "string",
                "description": "`ozav_wal_...`"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "USDC",
                  "USDT"
                ]
              },
              "chain": {
                "type": "string",
                "description": "The network the stablecoin arrives on, for example `polygon`."
              }
            }
          }
        }
      },
      "AgentAllocationMoveRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "amount"
        ],
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$"
          },
          "amount": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "amount",
              "currency"
            ],
            "properties": {
              "amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "USDC",
                  "USDT"
                ]
              }
            }
          }
        }
      },
      "AgentWalletRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "custody",
          "network"
        ],
        "properties": {
          "custody": {
            "type": "string",
            "enum": [
              "bring_your_own",
              "ozav_enclave"
            ],
            "description": "`bring_your_own`: a Safe the company deployed (needs `safe_address`). `ozav_enclave`: a wallet OZAV holds for the agent (takes no `safe_address`)."
          },
          "safe_address": {
            "type": "string",
            "description": "The Safe's address (`0x...`). Required with `bring_your_own`, refused with `ozav_enclave`."
          },
          "network": {
            "type": "string",
            "enum": [
              "base",
              "polygon"
            ],
            "description": "The network of the Safe, or the network to record the address for when OZAV holds the wallet (one wallet serves both)."
          }
        }
      },
      "AgentWalletWithdrawRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key"
        ],
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$"
          },
          "amount": {
            "type": "object",
            "description": "How much to withdraw. Omit it to withdraw the whole balance.",
            "additionalProperties": false,
            "required": [
              "amount",
              "currency"
            ],
            "properties": {
              "amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "USDC"
                ]
              }
            }
          }
        }
      },
      "AgentWalletWithdrawal": {
        "type": "object",
        "required": [
          "id",
          "object",
          "agent",
          "status",
          "trigger",
          "chain",
          "amount",
          "settled_amount",
          "refusal_code",
          "parts",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The withdrawal id (`ozav_awd_...`)."
          },
          "object": {
            "type": "string",
            "enum": [
              "agent_wallet_withdrawal"
            ]
          },
          "agent": {
            "type": "string",
            "description": "The agent id (`ozav_agt_...`)."
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "settled",
              "refused",
              "in_review",
              "empty"
            ],
            "description": "`processing`: parts are still to run (call again with the same key). `settled`: every part settled. A success answer carries only these two. `refused` and `in_review` come inside `details.withdrawal` of the `422 AGENT_WALLET_WITHDRAW_REFUSED` / `AGENT_WALLET_WITHDRAW_ABOVE_WALLET_LIMIT` and `503 AGENT_WALLET_WITHDRAW_OUTCOME_UNKNOWN` errors. `empty` is only a revocation that found the wallet empty."
          },
          "trigger": {
            "type": "string",
            "enum": [
              "api",
              "revocation"
            ]
          },
          "chain": {
            "type": "string",
            "enum": [
              "polygon"
            ]
          },
          "amount": {
            "$ref": "#/components/schemas/AgentWalletWithdrawalAmount"
          },
          "settled_amount": {
            "$ref": "#/components/schemas/AgentWalletWithdrawalAmount"
          },
          "refusal_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ABOVE_WALLET_LIMIT",
              "REFUSED",
              null
            ],
            "description": "Only when `status` is `refused`."
          },
          "parts": {
            "type": "array",
            "description": "The parts, in the order they run, each at most the wallet's sealed per-payment limit.",
            "items": {
              "type": "object",
              "required": [
                "amount",
                "status",
                "tx_hash"
              ],
              "properties": {
                "amount": {
                  "$ref": "#/components/schemas/AgentWalletWithdrawalAmount"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "processing",
                    "settled",
                    "refused",
                    "in_review"
                  ]
                },
                "tx_hash": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The Polygon transaction, once known."
                }
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AgentWalletWithdrawalAmount": {
        "type": "object",
        "required": [
          "amount",
          "currency"
        ],
        "properties": {
          "amount": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]{0,14})(\\.[0-9]{1,6})?$",
            "description": "Decimal USDC, trailing zeros removed. `0` when nothing has settled yet."
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDC"
            ]
          }
        }
      },
      "AgentWallet": {
        "type": "object",
        "required": [
          "object",
          "custody",
          "smart_accounts",
          "enclave_wallets"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "agent_wallet"
            ]
          },
          "custody": {
            "type": "string",
            "enum": [
              "ozav",
              "bring_your_own",
              "ozav_enclave"
            ]
          },
          "network": {
            "type": "string",
            "enum": [
              "base",
              "polygon"
            ],
            "description": "Only in the answer to `POST` with `custody: ozav_enclave`: the network asked for."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active"
            ],
            "description": "Only in the answer to `POST` with `custody: ozav_enclave`: `pending` until the wallet exists."
          },
          "address": {
            "type": "string",
            "description": "Only when `status` is `active`: the wallet's address (`0x...`), the same on Base and Polygon."
          },
          "per_payment_cap": {
            "type": "object",
            "description": "Only in the answer to `POST` with `custody: ozav_enclave`: the largest USDC payment the wallet will sign, sealed when it was created.",
            "required": [
              "amount_atomic",
              "asset",
              "decimals"
            ],
            "properties": {
              "amount_atomic": {
                "type": "string",
                "description": "Whole units of 10^-decimals, as text."
              },
              "asset": {
                "type": "string",
                "enum": [
                  "USDC"
                ]
              },
              "decimals": {
                "type": "integer",
                "enum": [
                  6
                ]
              }
            }
          },
          "enclave_wallets": {
            "type": "array",
            "description": "The addresses OZAV holds for this agent, one per network recorded.",
            "items": {
              "type": "object",
              "required": [
                "network",
                "address"
              ],
              "properties": {
                "network": {
                  "type": "string",
                  "enum": [
                    "base",
                    "polygon"
                  ]
                },
                "address": {
                  "type": "string"
                }
              }
            }
          },
          "smart_accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "network",
                "safe_address",
                "safe_version",
                "company_owner",
                "ozav_signer",
                "threshold",
                "verified_at"
              ],
              "properties": {
                "network": {
                  "type": "string",
                  "enum": [
                    "base",
                    "polygon"
                  ]
                },
                "safe_address": {
                  "type": "string"
                },
                "safe_version": {
                  "type": "string"
                },
                "company_owner": {
                  "type": "string",
                  "description": "The owner that is not OZAV: the company's key, or its agent's."
                },
                "ozav_signer": {
                  "type": "string",
                  "description": "OZAV's policy key, the other owner."
                },
                "threshold": {
                  "type": "integer",
                  "enum": [
                    2
                  ]
                },
                "verified_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "AgentAllocation": {
        "type": "object",
        "required": [
          "object",
          "agent_id",
          "balances"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "agent_allocation"
            ]
          },
          "agent_id": {
            "type": "string",
            "description": "The agent id (`ozav_agt_...`)."
          },
          "balances": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "currency",
                "allocated",
                "spent",
                "available"
              ],
              "properties": {
                "currency": {
                  "type": "string",
                  "enum": [
                    "USDC",
                    "USDT"
                  ]
                },
                "allocated": {
                  "$ref": "#/components/schemas/DecimalAmount"
                },
                "spent": {
                  "$ref": "#/components/schemas/DecimalAmount"
                },
                "available": {
                  "$ref": "#/components/schemas/DecimalAmount"
                }
              }
            }
          }
        }
      },
      "AgentPrepaidResourceRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "seller_domain",
          "unit",
          "units"
        ],
        "description": "Credit bought outside OZAV, registered for a grant's agents.",
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this registration."
          },
          "agent_id": {
            "type": "string",
            "description": "Only this agent of the grant may consume it (`ozav_agt_...`). Leave it out for every agent."
          },
          "seller_domain": {
            "type": "string",
            "maxLength": 253,
            "description": "The seller's host (`api.openai.com`). `allowed_domains` of the mandate applies to it."
          },
          "unit": {
            "type": "string",
            "pattern": "^[a-z0-9_]{1,32}$",
            "description": "What a unit is (`tokens`, `requests`, `credits`)."
          },
          "units": {
            "type": "string",
            "pattern": "^[1-9][0-9]{0,15}$",
            "description": "How many units were bought, a whole number as a string."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the seller stops honoring it. Leave it out if it does not expire."
          },
          "description": {
            "type": "string",
            "maxLength": 200,
            "description": "A note for the company."
          }
        }
      },
      "AgentPrepaidResource": {
        "type": "object",
        "required": [
          "id",
          "object",
          "grant",
          "agent",
          "seller_domain",
          "unit",
          "initial_units",
          "balance_units",
          "expires_at",
          "description",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The resource id (`ozav_ppr_...`)."
          },
          "object": {
            "type": "string",
            "const": "agent_prepaid_resource",
            "description": "Always the literal `agent_prepaid_resource`."
          },
          "grant": {
            "type": "string",
            "description": "The grant."
          },
          "agent": {
            "type": [
              "string",
              "null"
            ],
            "description": "The only agent that may consume it, or null for every agent of the grant."
          },
          "seller_domain": {
            "type": "string",
            "description": "The seller's host."
          },
          "unit": {
            "type": "string",
            "description": "What a unit is."
          },
          "initial_units": {
            "type": "string",
            "description": "The units registered."
          },
          "balance_units": {
            "type": "string",
            "description": "The units left. Only goes down."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it stops being consumable."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "The company's note."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "closed"
            ],
            "description": "A closed resource is not consumed."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it was registered."
          }
        }
      },
      "AgentConsumptionRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "resource_id",
          "units"
        ],
        "description": "What the agent consumed from a prepaid resource.",
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this consumption. The same key never debits twice."
          },
          "resource_id": {
            "type": "string",
            "description": "The prepaid resource (`ozav_ppr_...`)."
          },
          "units": {
            "type": "string",
            "pattern": "^[1-9][0-9]{0,15}$",
            "description": "The units consumed, a whole number as a string."
          }
        }
      },
      "AgentPayeeRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "currency",
          "rail",
          "account_holder",
          "details"
        ],
        "description": "The body of `POST /payout-destinations` without `customer_id` (the destination always belongs to the account holder of the grant), plus your key.",
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this registration. The same key returns the same destination and never creates a second one."
          },
          "currency": {
            "type": "string",
            "description": "The currency of the destination, for example `BRL`."
          },
          "rail": {
            "type": "string",
            "description": "The rail, for example `pix`."
          },
          "account_holder": {
            "type": "object",
            "description": "Who owns the account, as in `POST /payout-destinations`. `name` and `type` are required.",
            "required": [
              "name",
              "type"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "individual",
                  "business"
                ]
              },
              "tax_id": {
                "type": "object",
                "description": "The holder's document, as in `POST /payout-destinations`.",
                "properties": {
                  "country": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "value": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "details": {
            "type": "object",
            "description": "The account details of the rail, as in `POST /payout-destinations`."
          },
          "metadata": {
            "type": "object",
            "description": "Free-form key-value pairs, as in `POST /payout-destinations`."
          }
        }
      },
      "AgentYieldApplicationRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "strategy_id",
          "amount"
        ],
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this application. The same key returns the same recorded application and never applies twice."
          },
          "strategy_id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9-]{1,62}$",
            "description": "The strategy, from `GET /yield/strategies` (for example `base-usdc-prime`). It must be in the mandate's `yield_rules.allowed_strategies`."
          },
          "amount": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "amount",
              "currency"
            ],
            "description": "What is applied from the company's custodied balance.",
            "properties": {
              "amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "currency": {
                "type": "string",
                "pattern": "^[A-Z0-9]{2,10}$",
                "description": "The strategy's asset, for example `USDC`."
              }
            }
          }
        }
      },
      "AgentYieldApplication": {
        "description": "The `AgentAction` of a yield application, with the managed yield order as it is now.",
        "allOf": [
          {
            "$ref": "#/components/schemas/AgentAction"
          },
          {
            "type": "object",
            "required": [
              "order"
            ],
            "properties": {
              "order": {
                "description": "The managed yield order. `null` before it exists.",
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "id",
                      "status",
                      "detail",
                      "tx_hash"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "description": "The order id (the same as `yield_application.application_id`)."
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "pending",
                          "running",
                          "applied",
                          "refused",
                          "failed",
                          "indeterminate"
                        ],
                        "description": "`applied`: confirmed on chain. `refused` / `failed`: nothing moved. `indeterminate`: stopped without knowing whether money moved; OZAV checks the chain before it closes."
                      },
                      "detail": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Why it was refused or failed, when it was. Only the company reads it; `null` for the platform."
                      },
                      "tx_hash": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "The on-chain transaction, once known. Only the company reads it; `null` for the platform."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        ]
      },
      "AgentAccountOpeningRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "currency"
        ],
        "description": "The body of `POST /accounts` without `customer_id` (the account always belongs to the account holder of the grant), plus your key.",
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this opening. The same key returns the same account and never opens a second one."
          },
          "account_type": {
            "type": "string",
            "enum": [
              "named_fiat_account"
            ],
            "description": "As in `POST /accounts`. Optional."
          },
          "currency": {
            "type": "string",
            "description": "The account's currency, for example `BRL`, as in `POST /accounts`."
          },
          "country": {
            "type": "string",
            "description": "The account's country, as in `POST /accounts`. Required when the mandate's `account_rules` list countries."
          },
          "rail": {
            "type": "string",
            "description": "The account's rail, as in `POST /accounts`. Required when the mandate's `account_rules` list rails."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            },
            "description": "Free-form key-value pairs, as in `POST /accounts`. OZAV adds `ozav_agent` and `ozav_agent_action`."
          }
        }
      },
      "AgentAccountActionWithAccount": {
        "description": "The account opening, with the account it opened.",
        "allOf": [
          {
            "$ref": "#/components/schemas/AgentAction"
          },
          {
            "type": "object",
            "required": [
              "named_account"
            ],
            "properties": {
              "named_account": {
                "description": "The account as `GET /accounts/{account_id}` shows it; `null` when it could not be read right now.",
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Account"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        ]
      },
      "AgentAccountList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list"
          },
          "has_more": {
            "type": "boolean"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "object",
                "action_id",
                "status",
                "account_id",
                "currency",
                "country",
                "rail",
                "created_at",
                "named_account"
              ],
              "properties": {
                "object": {
                  "type": "string",
                  "const": "agent_account"
                },
                "action_id": {
                  "type": "string",
                  "description": "The opening action (`ozav_aga_...`)."
                },
                "status": {
                  "type": "string",
                  "description": "The opening action's status, as on `AgentAction`."
                },
                "account_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The account it opened (`ozav_acc_...`); `null` when it did not."
                },
                "currency": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "country": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "rail": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "named_account": {
                  "description": "The account as it is now; `null` when it was never opened or could not be read right now.",
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Account"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "AgentPayoutRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "idempotency_key",
          "payout_destination_id",
          "amount",
          "source"
        ],
        "properties": {
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this payout. The same key returns the same recorded payout and never pays twice."
          },
          "payout_destination_id": {
            "type": "string",
            "description": "The company's registered payout destination (`ozav_pd_...`)."
          },
          "amount": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "amount",
              "currency"
            ],
            "description": "What the beneficiary receives.",
            "properties": {
              "amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "currency": {
                "type": "string",
                "pattern": "^[A-Z]{3}$",
                "description": "ISO 4217 code, e.g. `BRL`."
              }
            }
          },
          "source": {
            "description": "Where the stablecoin leaves from, either the company's managed wallet or the agent's own wallet.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "wallet_id",
                  "currency",
                  "chain"
                ],
                "description": "The company's managed wallet the stablecoin leaves from.",
                "properties": {
                  "wallet_id": {
                    "type": "string",
                    "description": "`ozav_wal_...`"
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "USDC",
                      "USDT"
                    ]
                  },
                  "chain": {
                    "type": "string",
                    "description": "The network the funds leave from, e.g. `polygon`."
                  }
                }
              },
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "type",
                  "currency",
                  "chain"
                ],
                "description": "The agent's own wallet in OZAV's infrastructure (production, Polygon).",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "agent_wallet"
                    ]
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "USDC"
                    ]
                  },
                  "chain": {
                    "type": "string",
                    "enum": [
                      "polygon"
                    ]
                  }
                }
              }
            ]
          }
        }
      },
      "AgentActionException": {
        "type": "object",
        "description": "An exception case (dispute) on an agent action, with the evidence OZAV recorded when it was opened and, once decided, the remedy.",
        "required": [
          "id",
          "object",
          "action",
          "reason_code",
          "opened_by",
          "status",
          "due_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "`ozav_agx_...`"
          },
          "object": {
            "type": "string",
            "enum": [
              "agent_action_exception"
            ]
          },
          "action": {
            "type": "string",
            "description": "The agent action (`ozav_aga_...`)."
          },
          "reason_code": {
            "type": "string",
            "enum": [
              "not_delivered",
              "wrong_amount",
              "duplicate_payment",
              "not_authorized",
              "other"
            ]
          },
          "opened_by": {
            "type": "string",
            "enum": [
              "platform",
              "account_holder"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "under_review",
              "resolved_refunded",
              "resolved_denied",
              "closed_legacy"
            ],
            "description": "Forward only. A resolved case never reopens. `closed_legacy` is an older case (opened before cases had their own status) whose ticket was already closed; it has no recorded outcome or evidence."
          },
          "due_at": {
            "type": "string",
            "format": "date-time"
          },
          "evidence": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "What OZAV recorded from the action when the case was opened (action, type, mandate version and its SHA-256, decision, reasons, status, amounts, payee, receipt and its SHA-256, timestamps). Never changes."
          },
          "evidence_sha256": {
            "type": [
              "string",
              "null"
            ],
            "description": "SHA-256 (hex) of `evidence`."
          },
          "remedy": {
            "type": [
              "object",
              "null"
            ],
            "description": "Set when the case is resolved.",
            "properties": {
              "channel": {
                "type": "string",
                "enum": [
                  "pix_med",
                  "manual_transfer",
                  "onchain_payee_block",
                  "card_chargeback",
                  "none"
                ]
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "OZAV's protocol number for the remedy."
              }
            }
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "support_ticket": {
            "type": [
              "string",
              "null"
            ],
            "description": "OZAV's support ticket number."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AgentRequestSignatureRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "authority"
        ],
        "properties": {
          "authority": {
            "type": "string",
            "maxLength": 260,
            "description": "The host (and port, when not 443) the agent will call, as in the request's `Host` - `api.example.com`. No scheme, path or credentials."
          },
          "method": {
            "type": "string",
            "maxLength": 16,
            "description": "Optional. The HTTP method; when sent, the signature covers it."
          },
          "path": {
            "type": "string",
            "maxLength": 2048,
            "description": "Optional. The request path, starting with `/`, without query string or fragment; when sent, the signature covers it."
          },
          "signature_agent_format": {
            "type": "string",
            "enum": [
              "string",
              "dictionary"
            ],
            "description": "Optional. How `Signature-Agent` is written. `string` (the default) is `\"https://ozav.io\"`, the form Cloudflare verifies today; `dictionary` is `sig1=\"https://ozav.io\"`, the form of the IETF Web Bot Auth protocol."
          }
        }
      },
      "AgentRequestSignature": {
        "type": "object",
        "description": "The headers OZAV signed for one request of the agent.",
        "required": [
          "object",
          "agent",
          "headers",
          "created_at",
          "expires_at",
          "key_id",
          "directory_url",
          "simulated"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "agent_request_signature"
            ]
          },
          "agent": {
            "type": "string",
            "description": "The agent id (`ozav_agt_...`)."
          },
          "headers": {
            "type": "object",
            "description": "Add these headers to the request, unchanged.",
            "required": [
              "Signature-Agent",
              "Signature-Input",
              "Signature",
              "OZAV-Agent"
            ],
            "properties": {
              "Signature-Agent": {
                "type": "string",
                "description": "The directory origin: `\"https://ozav.io\"`, or `sig1=\"https://ozav.io\"` with `signature_agent_format: dictionary`."
              },
              "Signature-Input": {
                "type": "string"
              },
              "Signature": {
                "type": "string"
              },
              "OZAV-Agent": {
                "type": "string",
                "description": "The agent id",
                "covered by the signature.": null
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "60 seconds after `created_at`."
          },
          "key_id": {
            "type": "string",
            "description": "The SHA-256 thumbprint (RFC 7638) of OZAV's signing key."
          },
          "directory_url": {
            "type": "string",
            "description": "Where sites fetch OZAV's public keys."
          },
          "simulated": {
            "type": "boolean",
            "description": "`true` in the sandbox: no directory lists the key."
          }
        }
      },
      "AgentPassport": {
        "type": "object",
        "description": "A passport OZAV signed for an agent. The `credential` is what the agent presents.",
        "required": [
          "id",
          "object",
          "agent",
          "format",
          "credential",
          "issued_at",
          "expires_at",
          "jwks_url",
          "status_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The passport id, also the token's `jti` (`ozav_psp_...`)."
          },
          "object": {
            "type": "string",
            "enum": [
              "agent_passport"
            ]
          },
          "agent": {
            "type": "string",
            "description": "The agent id (`ozav_agt_...`)."
          },
          "format": {
            "type": "string",
            "enum": [
              "vc+sd-jwt"
            ]
          },
          "credential": {
            "type": "string",
            "description": "The SD-JWT in compact form: `<jwt>~<disclosure>~...~`."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "jwks_url": {
            "type": "string",
            "format": "uri",
            "description": "Where OZAV's public signing keys are published."
          },
          "status_url": {
            "type": "string",
            "format": "uri",
            "description": "Where anyone checks this passport's status, without an API key."
          },
          "sandbox": {
            "type": "boolean",
            "description": "Present and `true` on a sandbox (test) passport: signed with the sandbox key, never valid in production."
          }
        }
      },
      "Agent": {
        "type": "object",
        "required": [
          "id",
          "object",
          "label",
          "platform_agent_ref",
          "status",
          "grant",
          "grant_status",
          "platform",
          "parent_agent",
          "mandate_version",
          "mandate",
          "own_mandate",
          "can_transact",
          "revoked_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The agent id (`ozav_agt_...`)."
          },
          "object": {
            "type": "string",
            "const": "agent",
            "description": "Always the literal `agent`."
          },
          "label": {
            "type": "string",
            "description": "The agent's name."
          },
          "platform_agent_ref": {
            "type": "string",
            "description": "The agent's id on the platform."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "revoked"
            ],
            "description": "The agent's own status."
          },
          "grant": {
            "type": "string",
            "description": "The grant it acts under."
          },
          "grant_status": {
            "type": "string",
            "description": "The grant's status."
          },
          "platform": {
            "type": [
              "string",
              "null"
            ],
            "description": "The platform."
          },
          "parent_agent": {
            "type": [
              "string",
              "null"
            ],
            "description": "The parent agent, for a sub-agent."
          },
          "mandate_version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The grant's mandate version in force."
          },
          "mandate": {
            "description": "The grant's mandate in force.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/AgentMandate"
              },
              {
                "type": "null"
              }
            ]
          },
          "own_mandate": {
            "description": "The agent's own narrower mandate, if any.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/AgentMandate"
              },
              {
                "type": "null"
              }
            ]
          },
          "can_transact": {
            "type": "boolean",
            "description": "True when the agent and its grant are both active."
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the agent was revoked."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it was registered."
          }
        }
      },
      "AgentPaymentRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "protocol",
          "idempotency_key"
        ],
        "properties": {
          "protocol": {
            "type": "string",
            "enum": [
              "x402",
              "mpp",
              "pay_per_crawl"
            ],
            "description": "The protocol of the challenge. `pay_per_crawl` is Cloudflare's `crawler-price` form: send `resource_url` and `crawler_price` instead of a challenge header."
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your key for this payment. The same key returns the same request."
          },
          "payment_required_header": {
            "type": "string",
            "description": "x402 v2: the value of the `PAYMENT-REQUIRED` header."
          },
          "payment_required_body": {
            "type": "object",
            "description": "x402 v1: the JSON body of the 402 response."
          },
          "www_authenticate": {
            "type": "string",
            "description": "MPP: the value of the `WWW-Authenticate` header."
          },
          "resource_url": {
            "type": "string",
            "maxLength": 2048,
            "description": "`pay_per_crawl` only: the https URL of the page that answered 402. Its host is the signed `@authority`."
          },
          "crawler_price": {
            "type": "string",
            "maxLength": 64,
            "description": "`pay_per_crawl` only: the value of the `crawler-price` header, exactly as received (`USD 0.01`: the currency, one space, a positive amount with up to 2 decimals)."
          }
        }
      },
      "AgentAction": {
        "type": "object",
        "required": [
          "id",
          "object",
          "agent",
          "grant",
          "mandate_version",
          "nature",
          "action",
          "protocol",
          "amount_usd",
          "amount_atomic",
          "asset",
          "network",
          "spread",
          "payee",
          "domain",
          "decision",
          "reasons",
          "status",
          "receipt",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The action id (`ozav_aga_...`)."
          },
          "object": {
            "type": "string",
            "const": "agent_action",
            "description": "Always the literal `agent_action`."
          },
          "agent": {
            "type": "string",
            "description": "The agent."
          },
          "grant": {
            "type": "string",
            "description": "The grant."
          },
          "mandate_version": {
            "type": "integer",
            "description": "The mandate version it was checked against."
          },
          "nature": {
            "type": "string",
            "enum": [
              "new_money",
              "simulated",
              "prepaid"
            ],
            "description": "The nature of the money this action moves or asks for, set once when the action is created and never changed.\n`simulated` is sandbox money (nothing moved, nothing was reserved or signed), `new_money` is a production action\nthat moves or requests real money, `prepaid` is a consumption of credit the company bought outside OZAV\n(`prepaid_consumption`; no money moves and there is no dollar amount).\nFilter the list with `?nature=`.\n"
          },
          "action": {
            "type": "string",
            "enum": [
              "x402_payment",
              "mpp_payment",
              "fiat_payout",
              "fiat_funding",
              "payee_registration",
              "prepaid_consumption",
              "yield_application",
              "account_opening"
            ],
            "description": "What was requested."
          },
          "protocol": {
            "type": [
              "string",
              "null"
            ],
            "description": "`x402` or `mpp`."
          },
          "amount_usd": {
            "type": [
              "string",
              "null"
            ],
            "description": "The amount in US dollars, as a decimal string. On a `fiat_funding` it is the money coming in, and on a `yield_application` the amount applied to yield: neither is spending."
          },
          "amount_atomic": {
            "type": [
              "string",
              "null"
            ],
            "description": "The amount in the asset's smallest unit."
          },
          "asset": {
            "type": [
              "string",
              "null"
            ],
            "description": "The asset."
          },
          "network": {
            "type": [
              "string",
              "null"
            ],
            "description": "The network, as CAIP-2."
          },
          "spread": {
            "description": "OZAV's spread on this payment, charged to the account holder in addition to `amount_usd` / `amount_atomic`, which stay what the payee is paid. 5 bps (0.05%) by default; the rate is fixed when the payment is accepted and `amount_atomic * bps / 10000` is rounded down, so a payment too small for one unit of spread pays none (`amount_atomic` `0`). The account balance must cover the amount plus the spread. The spread is not counted against the grant's budget or limits: they bound what the agent pays out. A spread is released with the payment when it expires or fails, and refunded with a Cloudflare commitment that was not billed. `null` on a payment that carries none (requests made before the spread existed).",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "amount_atomic",
                  "amount_usd",
                  "currency",
                  "bps"
                ],
                "properties": {
                  "amount_atomic": {
                    "type": "string",
                    "description": "The spread in atomic USDC (6 decimals)."
                  },
                  "amount_usd": {
                    "type": "string",
                    "description": "The spread in US dollars, as an exact decimal string."
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "USDC"
                    ],
                    "description": "The spread is always in USDC."
                  },
                  "bps": {
                    "type": "integer",
                    "description": "The rate applied, in basis points."
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "payee": {
            "description": "Who is paid: an address on a network, or a registered payout destination. The same shape as `allowed_payees`.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "type",
                  "network",
                  "address"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "address"
                    ],
                    "description": "An on-chain address."
                  },
                  "network": {
                    "type": "string",
                    "description": "The CAIP-2 network (`eip155:137`)."
                  },
                  "address": {
                    "type": "string",
                    "description": "The address paid."
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "type",
                  "id"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "payout_destination"
                    ],
                    "description": "A registered payout destination."
                  },
                  "id": {
                    "type": "string",
                    "description": "The payout destination id (`ozav_pd_...`)."
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "domain": {
            "type": [
              "string",
              "null"
            ],
            "description": "The host of the resource paid, as the platform reported it. OZAV cannot verify it: it limits nothing on its own, and the mandate's payees and amounts are what bind the money."
          },
          "decision": {
            "type": "string",
            "enum": [
              "within_mandate",
              "needs_approval",
              "outside_mandate"
            ],
            "description": "What the mandate decided."
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Why it is outside the mandate, when it is: English codes such as `above_daily_limit`, `above_per_payment_limit`, `corridor_not_allowed`, `payee_not_allowed`, `domain_not_allowed`, `mandate_expired`, `above_recurring_budget`, `above_agent_allocation`."
          },
          "status": {
            "type": "string",
            "enum": [
              "refused",
              "awaiting_approval",
              "authorized",
              "signed",
              "settled",
              "executed",
              "awaiting_payment",
              "returned",
              "expired",
              "failed"
            ],
            "description": "Where the request is now. A payout moves from `executed` to `settled` when its transfer completes (the spend stays counted), or to `returned` when the transfer ended without delivering the money (`cancelled` or `expired`): the mandate budget is released. A transfer under review (including a Pix return) or one that failed stays `executed` until OZAV reviews it, and the receipt's `transfer_status` shows it (`failed`, `needs_review`)."
          },
          "receipt": {
            "description": "The settlement receipt, once there is one. For a signed stablecoin payment, OZAV asks the token contract whether the authorization was used: `authorization_used: true` with `checked_at` is the settlement proof, and `tx_hash` is the on-chain transaction that used it, when OZAV found it (evidence you can open in a block explorer; absent does not mean unsettled). For a local-currency payout, the transfer OZAV created (`transfer_id`, `transfer_status`, `quote_id`, `source_amount`, `target_amount`), or why it was refused before any money moved."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it was recorded."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it last changed."
          },
          "consumption": {
            "type": "object",
            "description": "Only on a `prepaid_consumption`: what was consumed, and what it left.",
            "required": [
              "resource_id",
              "units",
              "seller_domain",
              "unit",
              "balance_after"
            ],
            "properties": {
              "resource_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The prepaid resource (`ozav_ppr_...`)."
              },
              "units": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The units consumed, a whole number as a string."
              },
              "seller_domain": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The seller the resource was bought from."
              },
              "unit": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "What a unit is (`tokens`). Absent on a refusal."
              },
              "balance_after": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The balance this consumption left. Absent on a refusal."
              }
            }
          },
          "yield_application": {
            "type": "object",
            "description": "Only on a `yield_application`: the strategy and amount applied from the company's custodied balance, and the managed yield order once it exists. `amount_usd` carries the dollars (an application is not spending).",
            "required": [
              "strategy_id",
              "amount",
              "currency",
              "chain",
              "application_id"
            ],
            "properties": {
              "strategy_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The strategy (`GET /yield/strategies`)."
              },
              "amount": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The amount applied, as a decimal string in `currency`."
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The strategy's asset, for example `USDC`."
              },
              "chain": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The strategy's network, for example `base`."
              },
              "application_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The managed yield order. `null` until the order exists."
              }
            }
          },
          "account": {
            "type": "object",
            "description": "Only on `account_opening`: the named account the opening asked for, and the one it created (its `id` exists once the account does).",
            "required": [
              "id",
              "currency"
            ],
            "properties": {
              "id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The account (`ozav_acc_...`)."
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The account's currency."
              },
              "country": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only on an opening: the country asked."
              },
              "rail": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only on an opening: the rail asked."
              }
            }
          },
          "payment": {
            "type": "object",
            "description": "Only in the response that signed the payment: the header to send back to the resource. Delivered once.",
            "required": [
              "header_name",
              "header_value",
              "valid_before",
              "simulated"
            ],
            "properties": {
              "header_name": {
                "type": "string",
                "enum": [
                  "PAYMENT-SIGNATURE",
                  "X-PAYMENT",
                  "Authorization",
                  "crawler-exact-price"
                ],
                "description": "The header to set."
              },
              "header_value": {
                "type": "string",
                "description": "Its value."
              },
              "valid_before": {
                "type": "string",
                "format": "date-time",
                "description": "When the signed authorization stops being valid."
              },
              "simulated": {
                "type": "boolean",
                "description": "True in the sandbox: the signature is all zeros and no resource accepts it."
              },
              "headers": {
                "type": "object",
                "description": "Only on Cloudflare pay-per-crawl (`cloudflare:402`): every header to send with the request, the payment and the HTTP message signature that covers it. With `protocol: pay_per_crawl` they are `crawler-exact-price`, `signature-agent`, `signature-input` and `signature`.",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "payout": {
            "type": "object",
            "description": "Only on a local-currency payout: the transfer OZAV created for it.",
            "properties": {
              "transfer_id": {
                "type": "string",
                "description": "The `ozav_tr_` transfer, in the company's account."
              },
              "transfer_status": {
                "type": "string",
                "description": "The transfer's current status (`payout_initiated`, `completed`, `failed`, `needs_review`, and so on). OZAV keeps it up to date after the payout is created; a change arrives as `agent_action.updated` even when the action's own status does not move."
              },
              "source_type": {
                "type": "string",
                "enum": [
                  "agent_wallet"
                ],
                "description": "Present when the payout was funded from the agent's own wallet."
              },
              "quote_id": {
                "type": "string"
              },
              "source_amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "target_amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "simulated": {
                "type": "boolean",
                "description": "`true` in the sandbox: no money moved and nothing external was called."
              }
            }
          },
          "funding": {
            "type": "object",
            "description": "Only on a funding: the Pix charge OZAV created, in the company's account.",
            "properties": {
              "transfer_id": {
                "type": "string",
                "description": "The `ozav_tr_` transfer that settles when the company pays."
              },
              "transfer_status": {
                "type": "string",
                "description": "The transfer's status when this response was built."
              },
              "quote_id": {
                "type": "string"
              },
              "source_amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "source_currency": {
                "type": "string",
                "enum": [
                  "BRL"
                ]
              },
              "target_amount": {
                "$ref": "#/components/schemas/DecimalAmount"
              },
              "target_currency": {
                "type": "string",
                "enum": [
                  "USDC",
                  "USDT"
                ]
              },
              "simulated": {
                "type": "boolean",
                "description": "`true` in the sandbox: the Pix code is fake and no bank accepts it."
              },
              "payment_instructions": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "What to hand to whoever pays. Present while the charge can still be paid.",
                "properties": {
                  "rail": {
                    "type": "string",
                    "enum": [
                      "pix"
                    ]
                  },
                  "br_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The Pix copy-and-paste code."
                  },
                  "qr_code_image": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The QR code as a data URL."
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              }
            }
          },
          "exception": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AgentActionException"
              }
            ],
            "description": "Only in the webhook `agent_action.updated` sent when the action's exception case (dispute) is opened or changes status. Read the case with `GET /agent-actions/{action_id}/exceptions`."
          }
        }
      },
      "Decision": {
        "type": "object",
        "required": [
          "id",
          "object",
          "type",
          "effect",
          "reasons",
          "checks",
          "policy_version",
          "outcome",
          "outcome_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The decision id (`ozav_dec_...`)."
          },
          "object": {
            "type": "string",
            "const": "decision",
            "description": "Always the literal `decision`."
          },
          "type": {
            "type": "string",
            "description": "The decision type requested.",
            "enum": [
              "name.match",
              "destination.ownership",
              "wallet.risk",
              "transaction.outbound"
            ]
          },
          "effect": {
            "type": "string",
            "description": "`ALLOW`, `REVIEW` (a person should look) or `DENY` (OZAV holds the proof of the risk).",
            "enum": [
              "ALLOW",
              "REVIEW",
              "DENY"
            ]
          },
          "reasons": {
            "type": "array",
            "description": "Why. When `effect` is not `ALLOW`, only the reasons that caused it.",
            "items": {
              "type": "object",
              "required": [
                "code",
                "message"
              ],
              "properties": {
                "code": {
                  "type": "string",
                  "description": "Stable machine code, e.g. `NAME_MATCHES_SANCTIONS_LIST`."
                },
                "message": {
                  "type": "string",
                  "description": "A human-readable sentence."
                }
              }
            }
          },
          "checks": {
            "type": "array",
            "description": "Each check that ran and what it found.",
            "items": {
              "type": "object",
              "required": [
                "check",
                "result"
              ],
              "properties": {
                "check": {
                  "type": "string",
                  "description": "`sanctions_list`, `designated_addresses` or `destination_ownership`."
                },
                "result": {
                  "type": "string",
                  "description": "What the check found."
                }
              }
            }
          },
          "policy_version": {
            "type": "string",
            "description": "The version of OZAV's rules that produced the verdict."
          },
          "outcome": {
            "type": [
              "string",
              "null"
            ],
            "description": "The outcome you reported, or `null`.",
            "enum": [
              "confirmed_risk",
              "false_positive",
              "completed",
              "reversed",
              "chargeback",
              null
            ]
          },
          "outcome_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the outcome was reported."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the decision was made."
          }
        }
      },
      "ListEntry": {
        "type": "object",
        "required": [
          "id",
          "object",
          "list",
          "type",
          "value_hint",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The entry id (`ozav_lst_...`)."
          },
          "object": {
            "type": "string",
            "const": "list_entry",
            "description": "Always the literal `list_entry`."
          },
          "list": {
            "type": "string",
            "description": "`block` or `allow`.",
            "enum": [
              "block",
              "allow"
            ]
          },
          "type": {
            "type": "string",
            "description": "The value type.",
            "enum": [
              "name",
              "document",
              "email",
              "email_domain"
            ]
          },
          "action": {
            "type": [
              "string",
              "null"
            ],
            "description": "`review` or `decline` for block entries; `null` for allow entries.",
            "enum": [
              "review",
              "decline",
              null
            ]
          },
          "value_hint": {
            "type": "string",
            "description": "A masked hint of the value (e.g. `J*** D***`, `•••789`, `j***@***`). Never the full value, never an email domain; a document shorter than 8 characters shows only `•••`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the entry was created."
          }
        }
      },
      "ListEntryDeleted": {
        "type": "object",
        "required": [
          "id",
          "object",
          "deleted"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The entry id (`ozav_lst_...`)."
          },
          "object": {
            "type": "string",
            "const": "list_entry",
            "description": "Always the literal `list_entry`."
          },
          "deleted": {
            "type": "boolean",
            "const": true,
            "description": "Always `true`."
          }
        }
      },
      "EvidencePack": {
        "type": "object",
        "description": "What OZAV verified and decided about one customer, assembled at request\ntime. An empty section means absence of record, never approval.\n",
        "properties": {
          "clienteRef": {
            "type": "string",
            "description": "The customer id this record is about (`ozav_cus_...`)."
          },
          "geradoEm": {
            "type": "string",
            "format": "date-time",
            "description": "When this record was assembled."
          },
          "veredito": {
            "type": "string",
            "enum": [
              "aprovado",
              "em_analise",
              "bloqueado",
              "sem_registro"
            ],
            "description": "`sem_registro` means the customer has not been verified - it is not an approval."
          },
          "fundamento": {
            "type": "string",
            "description": "Why this verdict, in one sentence."
          },
          "secoesIndisponiveis": {
            "type": "array",
            "description": "Sections that could not be read. Present means the record does not assert them.",
            "items": {
              "type": "string"
            }
          },
          "resumo": {
            "type": "object",
            "properties": {
              "verificacoes": {
                "type": "integer"
              },
              "triagens": {
                "type": "integer"
              },
              "documentos": {
                "type": "integer"
              },
              "decisoes": {
                "type": "integer"
              },
              "alertas_abertos": {
                "type": "integer"
              },
              "congelamentos_ativos": {
                "type": "integer"
              },
              "decisoes_com_desfecho": {
                "type": "integer",
                "description": "How many decisions already carry what happened afterwards."
              }
            }
          },
          "verificacoes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "capacidade": {
                  "type": "string"
                },
                "resultado": {
                  "type": "string"
                },
                "concluida_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              }
            }
          },
          "triagens": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "tipo": {
                  "type": "string"
                },
                "houve_correspondencia": {
                  "type": "boolean"
                },
                "nivel_de_risco": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "triada_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "decidida_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              }
            }
          },
          "documentos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "categoria": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "veredito": {
                  "type": "string"
                },
                "avaliador": {
                  "type": "string"
                },
                "avaliado_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "parser": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Which assessor version produced the verdict."
                }
              }
            }
          },
          "decisoes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "fluxo": {
                  "type": "string"
                },
                "efeito": {
                  "type": "string"
                },
                "barrou": {
                  "type": "boolean"
                },
                "movimento": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The movement this decision governed."
                },
                "decidida_em": {
                  "type": "string",
                  "format": "date-time"
                },
                "desfecho": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "What happened afterwards. `null` means not yet known."
                }
              }
            }
          },
          "alertas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "tipo": {
                  "type": "string"
                },
                "severidade": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "estado": {
                  "type": "string"
                },
                "aberto_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              }
            }
          },
          "congelamentos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "estado": {
                  "type": "string"
                },
                "escopo": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "congelado_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "liberado_em": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              }
            }
          },
          "atestacaoDeReliance": {
            "type": [
              "object",
              "null"
            ],
            "description": "Present when the integrator performed and attested the identity verification itself.",
            "properties": {
              "metodo": {
                "type": "string"
              },
              "emitida_em": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "expira_em": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        }
      },
      "ScreeningSignal": {
        "type": "object",
        "description": "One explained finding of a screening.",
        "required": [
          "code",
          "category",
          "severity",
          "evidence",
          "why_it_matters"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine code of the finding. A plain string, not a closed enum: new codes are\nadded over time, so treat an unknown code by its `category` and `severity`. Known values:\n- `sanctions_match` / `sanctions_possible_match` - strong / partial sanctions-list name match.\n- `pep_match` / `pep_possible_match` - strong / partial PEP-register name match.\n- `disposable_email` - disposable (temporary) mailbox service.\n- `email_domain_not_found` - the email domain does not exist (businesses).\n- `email_domain_no_mx` - the email domain has no mail server (businesses).\n- `email_domain_dns_unresolved` - the email domain's DNS did not answer; not evaluated.\n- `invalid_email_syntax` - not a name@domain address.\n- `recently_registered_domain` - company domain registered less than 90 days ago.\n- `free_email_for_business` - a company using a free email provider.\n- `domain_without_spf_dmarc` - company domain publishes neither SPF nor DMARC.\n- `domain_unrelated_to_name` - company domain contains no word of the company name.\n- `invalid_phone` - not a valid number for the given country.\n- `non_personal_phone` - virtual, toll-free or premium-rate line.\n- `phone_country_mismatch` - calling code differs from the declared country.\n- `org_blocklist_match` - the subject is on your organization's block list.\n- `org_allowlist_match` - the subject is on your organization's allow list; contact signals\n  were suppressed (block-list, sanctions and PEP matches never are).\n- `org_lists_unavailable` - your own lists could not be checked; screen again shortly.\n- `pep_exact_name_only` - the name is too common on the PEP register to be compared word\n  by word, so it was compared by exact name and aliases only. `result` is `review`, and\n  this is not transient: retrying returns the same. Have a person review the subject.\n"
          },
          "category": {
            "type": "string",
            "description": "Where the finding comes from:\n- `sanctions_list` - a sanctions / designated-persons list.\n- `pep_list` - a politically-exposed-persons register.\n- `contact` - the email or phone sent with the subject.\n- `org_list` - your organization's own block/allow lists (`/lists/entries`).\n",
            "enum": [
              "sanctions_list",
              "pep_list",
              "contact",
              "org_list"
            ]
          },
          "severity": {
            "type": "string",
            "description": "How much the finding weighs. Every list signal (`sanctions_list`, `pep_list`) is `high`.\n- `high` - escalates the decision on its own.\n- `medium` - below the escalation threshold; a human weighs it with the rest.\n- `low` - informational.\n",
            "enum": [
              "high",
              "medium",
              "low"
            ]
          },
          "evidence": {
            "type": "string",
            "description": "What was seen, in plain language."
          },
          "why_it_matters": {
            "type": "string",
            "description": "Why this finding matters for due diligence."
          }
        }
      },
      "Screening": {
        "type": "object",
        "description": "A point-in-time designated-list screening of a subject.",
        "required": [
          "id",
          "object",
          "subject",
          "status",
          "result"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque screening identifier, prefixed `ozav_scr_`."
          },
          "object": {
            "type": "string",
            "const": "screening",
            "description": "Resource type discriminator; always the literal `screening`."
          },
          "subject": {
            "$ref": "#/components/schemas/ScreeningSubject"
          },
          "lists": {
            "type": "array",
            "description": "The designated lists that were requested / screened against. Each item is one of:\n- `sanctions` - consolidated sanctions / designated-persons lists.\n- `pep` - politically exposed persons.\n- `adverse_media` - negative-news screening.\n",
            "items": {
              "type": "string",
              "enum": [
                "sanctions",
                "pep",
                "adverse_media"
              ]
            }
          },
          "status": {
            "type": "string",
            "description": "Processing state of the screening:\n- `pending` - the screening is still running.\n- `completed` - the screening finished; read the outcome from `result`.\n",
            "enum": [
              "pending",
              "completed"
            ]
          },
          "result": {
            "type": "string",
            "description": "Conservative, fail-closed outcome:\n- `clear` - every requested list was fully consulted with zero matches.\n- `match_found` - a strong (exact/alias) designated-list match.\n- `review` - a weaker match, or a requested list that could not be fully\n  consulted; never silently cleared.\n\nTelling the two `review` causes apart, because they call for opposite actions and the\nanswer is already in this response - read `matches`:\n\n- `review` with a non-empty `matches` - a weak (token-level) hit. A human decides.\n  Retrying changes nothing; the same subject screens the same way.\n- `review` with an empty `matches` - every list you requested came back without a hit,\n  and at least one of them could not be fully consulted. Nobody matched anything. This\n  is transient on our side: retry later rather than queueing the subject for review.\n  One exception, flagged in `signals`: `pep_exact_name_only` means the name is too\n  common on the PEP register for a word-by-word comparison, so only exact name and\n  aliases were compared. That is not transient - retrying returns the same; queue the\n  subject for human review.\n\nThe two are exhaustive for `review`, by construction: a weak hit is counted from the\nsame array that `matches` is serialized from, so an empty `matches` leaves an\nunconsultable source as the only remaining cause.\n\nWhich source degraded is not published. It is an operational fact about OZAV's\ningestion, not about your subject, and the action it implies (retry) is the same\nwhichever one it was.\n",
            "enum": [
              "clear",
              "match_found",
              "review"
            ]
          },
          "matches": {
            "type": "array",
            "description": "The matches found. Empty when the outcome is `clear` - and also empty on the\n`review` that means a list could not be consulted, which is why `result` and this\nfield have to be read together. See `ScreeningMatch`.\n",
            "items": {
              "$ref": "#/components/schemas/ScreeningMatch"
            }
          },
          "suggested_decision": {
            "type": "string",
            "description": "OZAV's suggested decision for this subject, computed and stored when the screening ran\n(reading it later returns that decision, not a recomputation). It only escalates:\n- `decline` - a strong (exact/alias) sanctions match corroborated by a second attribute:\n  the `date_of_birth` you sent matches the date on the list entry. A name-only match -\n  no date on either side, or a different date - is `review`, never `decline`, because a\n  homonym must not be declined automatically.\n  Consequences: a `business` subject cannot reach `decline` (businesses carry no date\n  of birth), and a `customer_id` subject is screened by name only today (the\n  customer's stored date of birth is not used) - both stop at `review`.\n- `review` - any list signal (every sanctions or PEP signal escalates), a `high`\n  contact signal, or a `review` result (see `result`).\n- `clear` - no list match, every requested list consulted, and any contact signals\n  below the escalation threshold (`medium`/`low` contact signals may be present).\n`result` keeps its list-only meaning; `suggested_decision` also weighs contact signals.\n",
            "enum": [
              "clear",
              "review",
              "decline"
            ]
          },
          "signals": {
            "type": "array",
            "description": "Every finding behind `suggested_decision`, explained: what was seen and why it matters.\nList matches appear here too (with `category` `sanctions_list` or `pep_list`), next to\ncontact signals (`contact`) when email/phone were sent and contact signals are enabled.\nAn empty array is only \"nothing found\" together with `contact_evaluated` (see below).\n",
            "items": {
              "$ref": "#/components/schemas/ScreeningSignal"
            }
          },
          "contact_evaluated": {
            "type": "boolean",
            "description": "`true` when the email/phone you sent were fully evaluated. `false` when no email or\nphone was sent, contact signals are disabled on the OZAV platform (a platform-wide\nsetting, not per account), the evaluation failed, or the email domain's DNS did not\nanswer in time (then `email_domain_dns_unresolved` appears in `signals`). With `false`,\nthe absence of contact signals says nothing about the contact data.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the screening was created (RFC 3339 / ISO 8601, UTC)."
          }
        }
      },
      "Verification": {
        "type": "object",
        "description": "An identity / business verification session (KYC / KYB) for a customer.\nCompleting or simulating a verification updates the customer's KYC\nstatus. In sandbox, approval can materialize simulated wallet and\nBRL/Pix account projections. Live wallet provisioning and live bank\naccount opening remain separate enabled steps.\n",
        "required": [
          "id",
          "object",
          "customer_id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque verification identifier, prefixed `ozav_vrf_`."
          },
          "object": {
            "type": "string",
            "const": "verification",
            "description": "Resource type discriminator; always the literal `verification`."
          },
          "customer_id": {
            "type": "string",
            "description": "The `ozav_cus_` id of the customer being verified."
          },
          "session_url": {
            "type": "string",
            "format": "uri",
            "description": "Hosted verification session URL - redirect the end-user here to complete identity\ncapture (government ID, selfie, liveness; KYB/UBO for a business customer).\n\n**In sandbox this field never appears.** A `test` key never starts a hosted\nsession, because a real session bills a third party per attempt. Exercise the outcome\nwith `POST /verifications/{verification_id}/simulate` instead - that is the sandbox\naffordance, and it lets you drive `approved` or `rejected` yourself.\n\n**With a `live` key, two things must both be true**, and the second is not something you\ncontrol:\n\n1. Your `capabilities` include at least one that hosted capture covers:\n   `kyc_document`, `liveness`, `face_match`, `age_estimation`, `kyb`, `ubo`. Asking only\n   for list/data checks (`aml_name_screening`, `email_verify`, `economic_capacity`, ...)\n   never starts a session, because there is nothing for the end-user to do.\n2. Hosted capture is switched on at OZAV. This is a single platform-wide setting,\n   **not** something enabled per organization - there is nothing to request for your\n   account. While it is off, no `live` key starts a session.\n\nWhen either condition is missing - or the key is a `test` key - the verification is still\ncreated and is a valid record: it simply stays `pending` with `checks` at `not_started`\nand **no `session_url`**. That is not an error and retrying will not produce one.\n\nThe field is never fabricated: if the session cannot be created, the request fails with a\n`503` and nothing is persisted, rather than returning a record with a broken link. The\n`code` says whether retrying helps: `VERIFICATION_SESSION_UNAVAILABLE` (retry shortly),\n`VERIFICATION_TEMPORARILY_UNAVAILABLE` (on OZAV's side, not caused by your data - retry\nlater with the same data), `VERIFICATION_PROVIDER_REJECTED` or\n`VERIFICATION_PROVIDER_NOT_CONFIGURED` (contact OZAV support with the `request_id`).\n"
          },
          "status": {
            "type": "string",
            "description": "Overall state of the verification session:\n- `pending` - created; checks have not started.\n- `in_progress` - one or more checks are running.\n- `completed` - every check reached an outcome.\n- `expired` - the session expired before completing.\n",
            "enum": [
              "pending",
              "in_progress",
              "completed",
              "expired"
            ]
          },
          "checks": {
            "type": "object",
            "description": "Per-capability check results, keyed by `CapabilityKind`.",
            "additionalProperties": {
              "$ref": "#/components/schemas/CheckResult"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the verification session was created (RFC 3339 / ISO 8601, UTC)."
          }
        }
      },
      "Statement": {
        "type": "object",
        "description": "A monthly service statement. Amounts are in USD.",
        "required": [
          "object",
          "id",
          "month",
          "status",
          "volume_usd",
          "transactions",
          "active_users",
          "spread_client_share_usd",
          "amount_due_usd",
          "amount_credit_usd",
          "issued_at",
          "due_at",
          "paid_at",
          "grace_period",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "statement",
            "description": "Resource type discriminator; always `statement`."
          },
          "id": {
            "type": "string",
            "description": "`ozav_stmt_` followed by the year and month.",
            "example": "ozav_stmt_202609"
          },
          "month": {
            "type": "string",
            "description": "The month the statement covers (YYYY-MM).",
            "example": "2026-09"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "issued",
              "overdue",
              "paid",
              "cancelled"
            ],
            "description": "`issued` past its due date reads as `overdue`."
          },
          "volume_usd": {
            "type": "number",
            "description": "Volume processed in the month."
          },
          "transactions": {
            "type": "integer",
            "description": "Number of transactions in the month."
          },
          "active_users": {
            "type": "integer",
            "description": "Customers active in the month."
          },
          "spread_client_share_usd": {
            "type": "number",
            "description": "Your share of the spread for the month."
          },
          "amount_due_usd": {
            "type": "number",
            "description": "What you owe OZAV for the month."
          },
          "amount_credit_usd": {
            "type": "number",
            "description": "What OZAV owes you for the month."
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the statement was issued."
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When payment is due."
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was paid."
          },
          "grace_period": {
            "type": "boolean",
            "description": "Whether the month fell in a grace period with no charge."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the statement was generated."
          }
        }
      },
      "StatementList": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list",
            "description": "Always `list`."
          },
          "data": {
            "type": "array",
            "description": "The statements, newest first, up to the last 36 months.",
            "items": {
              "$ref": "#/components/schemas/Statement"
            }
          },
          "has_more": {
            "type": "boolean",
            "description": "Always `false`; the list is not paginated."
          },
          "next_cursor": {
            "type": "null",
            "description": "Always `null`."
          }
        }
      },
      "StatementDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Statement"
          },
          {
            "type": "object",
            "required": [
              "lines"
            ],
            "properties": {
              "lines": {
                "type": "array",
                "description": "The itemized lines of the statement.",
                "items": {
                  "type": "object",
                  "required": [
                    "component",
                    "payment_rail",
                    "quantity",
                    "unit_price_usd",
                    "amount_usd"
                  ],
                  "properties": {
                    "component": {
                      "type": "string",
                      "description": "What the line charges or credits."
                    },
                    "payment_rail": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The payment rail",
                      "when the line is per rail.": null
                    },
                    "quantity": {
                      "type": "number",
                      "description": "Quantity billed."
                    },
                    "unit_price_usd": {
                      "type": "number",
                      "description": "Price per unit."
                    },
                    "amount_usd": {
                      "type": "number",
                      "description": "Line total."
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "ApiKey": {
        "type": "object",
        "description": "An API key's metadata - never its secret, which is shown only at creation and rotation.",
        "required": [
          "id",
          "object",
          "key_prefix",
          "environment",
          "scopes"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque API-key identifier, prefixed `ozav_key_`."
          },
          "object": {
            "type": "string",
            "const": "api_key",
            "description": "Resource type discriminator; always the literal `api_key`."
          },
          "name": {
            "type": "string",
            "description": "Human-readable label you gave the key."
          },
          "key_prefix": {
            "type": "string",
            "description": "Non-secret display prefix of the key (e.g. `ozav_sk_live_a1b2`) - enough to identify it without revealing the secret."
          },
          "environment": {
            "type": "string",
            "description": "Which environment the key operates against:\n- `test` - the sandbox; moves no real money.\n- `live` - production; can move real money.\n",
            "enum": [
              "test",
              "live"
            ]
          },
          "scopes": {
            "type": "array",
            "description": "The scopes granted to this key (intersected with the organization's entitlements at request time).",
            "items": {
              "type": "string"
            }
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "operator",
              "viewer"
            ],
            "description": "The role this key was minted with, when it was minted with one. It is\na label and a mint-time template - **not** part of the permission\ndecision, which stays `entitlements ∩ scopes`. A rotated key keeps it.\n\n**Absent** on a key whose scopes were listed one by one, which is\nevery key created before roles existed. The field is omitted rather\nthan sent as `null`, because `null` here would read as \"this key has\nno powers\" instead of \"this key was not minted from a template\".\n"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the key was last used to authenticate a request (RFC 3339 / ISO 8601, UTC)."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the key expires, if it has an expiry (e.g. an old secret's cutover time after a rotation)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the key was created (RFC 3339 / ISO 8601, UTC)."
          }
        }
      },
      "ApiKeyWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "The full secret key; returned only on creation and rotation."
              }
            }
          }
        ]
      },
      "WebhookEventType": {
        "type": "string",
        "description": "Event types currently emitted by API v2 webhooks. The catalog lists only\nevents that actually fire today:\n- `webhook.test` - queued by `POST /webhook-endpoints/{id}/test`.\n- `customer.created` / `customer.updated` - emitted by the v2 customers surface.\n- `verification.created` / `verification.updated` - emitted by the v2 verification surface.\n- `screening.completed` - emitted when a screening completes.\n- `transfer.updated` / `transfer.completed` / `transfer.failed` - emitted by the v2 transfer surface.\n- `card.created` / `card.updated` - **card lifecycle only.** `card.created` fires on\n  `POST /cards`; `card.updated` on a spending-controls change (`PATCH /cards/{id}`), on\n  freeze / unfreeze / close, and when the card processor changes the card's status on\n  its side: it confirms a `pending` card (now `active`), or freezes, closes (e.g. reported\n  lost) or expires it. A processor change can arrive without any call of yours. Apart from\n  confirming a `pending` card, it only ever restricts the card, never reopens it.\n- `card.fraud_confirmed` - emitted when OZAV reviews declines on one of your cards and\n  confirms they were fraud. `data` is `{ card, confirmed_at }`, with `card` in the same\n  shape as `card.updated`. **It is a notice, not an action:** the card is not frozen or\n  closed by it. Deciding what to do with the card is yours - `POST /cards/{card_id}/freeze`\n  or `/close`. It fires at most once per review, is never sent for a decline OZAV judged\n  legitimate, and carries no internal identifiers.\n- `card.fraud_case_closed` - emitted when OZAV finishes reviewing declines on one of your\n  cards, whatever the outcome. `data` is `{ card, outcome, fraud_category, decline_ids,\n  closed_at }`, with `card` in the same shape as `card.updated` and `outcome` one of\n  `fraud_confirmed`, `false_positive` (the declines were legitimate purchases: the\n  cardholder can try again) or `inconclusive`. One review covers the declines of one fraud\n  rule on the card: `decline_ids` lists them with the same `id`s that\n  `GET /cards/{card_id}/declines` returns, oldest first, once per purchase even if the\n  card processor retried it. If the review covered more than 500, these are the oldest\n  500; the rest are on `/declines`. A decline with no `card.declined` (for example, the\n  card had no public id yet, or the card processor sent no authorization reference) is not\n  listed, so the list can be empty. `fraud_category` is the kind of rule, with the same\n  values as on `card.declined`, and `null` if that rule no longer exists. A `fraud_confirmed` review also sends `card.fraud_confirmed`. It fires at\n  most once per review and carries no internal identifiers.\n\n\n- `card.authorized` - emitted when a purchase on one of your cards is **approved** and the\n  amount is held. `data` is `{ card_id, transaction_id, amount_usd, merchant }`. It is sent\n  after the decision has already been returned to the card processor, never inside the\n  processor's authorisation window, so subscribing to it cannot slow down or decline a\n  purchase.\n- `card.settled` - emitted when a purchase **clears**. `data` is\n  `{ card_id, transaction_id, amount_usd, settled_at, merchant }`. `amount_usd` is the\n  final amount, which can differ from the authorised one (tips, fuel adjustments) - use this\n  one for your ledger, not the authorised figure.\n\n\n  `transaction_id` (`ozav_ctxn_...`) is the `id` of the same purchase in\n  `GET /cards/{card_id}/authorizations` and `GET /cards/{card_id}/transactions`, so the\n  authorisation, its settlement and the row in your ledger reconcile on one key. A settlement\n  of an approved hold carries the same value as its `card.authorized`, including a settlement\n  that reaches OZAV before the hold was recorded. Only a second capture against the same\n  authorisation (a split shipment) gets its own row and its own id. On `card.authorized` it\n  is `null` when the hold row does not exist or could not be read when the event was sent\n  (the authorisation was released first, or recording the hold failed): read\n  `/authorizations`, or wait for its `card.settled`.\n\n\n  `card.settled` can arrive before `card.authorized` for the same purchase. When both carry\n  the same `transaction_id`, upsert on it and never move a settled row back to authorised.\n\n\n  Both carry the card's public id, never an internal identifier, and are de-duplicated on\n  the processor's own reference: a redelivered authorisation or settlement does not notify\n  you twice. A card that has never been seen through the v2 API has no public id, so no\n  event is sent for it.\n\n\n- `card.declined` - emitted when a purchase on one of your cards is **refused**. `data` is\n  `{ card_id, amount_usd, decline_code, fraud_category, merchant }`. A purchase OZAV refuses\n  leaves no hold and no row in `/authorizations`; it is listed on\n  `GET /cards/{card_id}/declines`, the same refusal this event announced.\n\n\n  `fraud_category` says which kind of fraud rule refused the purchase, and is `null` for\n  every `decline_code` other than `fraud_rule`. It is one of `velocity` (too many purchases\n  or too much spend in a short window), `transaction_amount` (one purchase above a limit),\n  `period_spend` (daily or monthly spend above a limit), `merchant_category`,\n  `merchant_country`, `time_of_day`, or `other`. The rule's own name and text are never\n  sent.\n\n\n  `decline_code` is one of: `insufficient_balance`, `card_frozen`, `card_inactive`,\n  `account_inactive`, `spending_limit`, `mcc_blocked`, `country_blocked`,\n  `presence_blocked`, `fraud_rule`, or `temporarily_unavailable`.\n\n\n  When the refusal is on our side rather than the cardholder's,\n  you get `temporarily_unavailable` instead of our internal reason. It means the purchase\n  can be retried and the cardholder did nothing wrong - which is the only part you can act\n  on. We never send the operator-facing text behind a decision.\n- `card.refunded` - emitted when a merchant **returns** money to one of your cards. `data`\n  is `{ card_id, transaction_id, amount_usd, refunded_at, merchant }`, with\n  `transaction_id` the refund's own row in `GET /cards/{card_id}/transactions`.\n\n\n  `amount_usd` is **positive**: the direction travels in the event type, not in the sign,\n  for the same reason `/transactions` reports `direction: credit` rather than a negative\n  number. A refund is not an authorisation - the merchant returns and the value credits,\n  with nobody asked - so it never appears in `/authorizations`.\n- `card.reversed` - emitted when a held authorisation is released **without settling**:\n  the merchant cancelled it, it expired, or the card issuer declined it after OZAV\n  approved. The held amount and its surcharge return to the balance. `data` is `{ card_id,\n  transaction_id, amount_usd, client_surcharge_usd, reason, reversed_at }`, with\n  `transaction_id` the same as in the `card.authorized` it closes (null if the hold was\n  never recorded) and `reason` one of `reversed`, `expired` or `declined`. In the sandbox,\n  trigger it with `POST /cards/{card_id}/simulate/reversal`.\n\n\n  In the sandbox you create one with `POST /cards/{card_id}/simulate/purchase` and\n  `is_refund: true` in the body.\n- `account.updated` - emitted when a named account's public status changes, so an\n  integrator learns their customer can receive money without polling GET /accounts.\n- `account.credited` - emitted when money arrives in a customer's BRL named account.\n  `data` is `{ account_id, movement }`, with `movement` in the same shape\n  `GET /accounts/{account_id}/movements` lists it, and the same `id`. The credit is\n  read from the account's statement every 10 minutes, so the event can arrive up to\n  that long after the payer sent the Pix. It fires once per credit. In the sandbox\n  you create one with `POST /accounts/{account_id}/simulate/credit`.\n- `deposit.credited` - emitted when crypto sent to one of your wallets has been credited\n  and is spendable. Receiving crypto needs no call from you: the arrival is observed on\n  chain and credited on its own, and it already shows up in `GET /transactions` and in\n  `GET /wallets/{wallet_id}/balances`. This event is the push, so you do not have to poll\n  for it. There is no `deposit.pending`: before a deposit is credited it can\n  still reorganize on chain, and telling you early would invite you to act on value that\n  may vanish.\n- `requirement.due` - emitted when compliance review determines which documents are still\n  missing for a customer, so you learn what to collect without polling. Body:\n  `{customer_id, object: requirement_request, due_at, reason, reason_label,\n  documents: [{key, label, destination?}], purpose?, missing_fields?}`. With\n  `reason: destination_unidentified` (`purpose: travel_rule`) each entry of\n  `documents` carries `destination: {chain, address}`: say who holds that wallet with\n  `POST /customers/{customer_id}/destination-identifications`. When the ask is for registration\n  data rather than a file (`reason: registration_incomplete` or `data_inconsistent`),\n  `missing_fields` lists what to send, in the same tokens as\n  `requirements.missing_fields` (e.g. `phone`, `residential_address`,\n  `expected_monthly_volume_usd`): send them with `PATCH /customers/{customer_id}` (or, for\n  `owners`, `POST /customers/{customer_id}/associated-persons`). Absent when nothing asked\n  for is a field. `tax_id` / `registration` there cannot be corrected once the customer\n  is approved or frozen (`409 TAX_ID_IMMUTABLE`): create a new customer instead. Emitted\n  once per ask, like a document ask. There is no\n  `requirement.satisfied`: the customer's own `GET /customers/{id}` shrinks\n  `requirements.documents_due` when a file lands, and announcing a second event would need\n  a second emitter to avoid becoming an empty promise.\n- `document.reviewed` - emitted when OZAV's own document review accepts or rejects a\n  supporting document (proof of address, bank statement, proof of income, company\n  document): `{customer_id, document_id, key, label, status: accepted|rejected, reason,\n  reason_label, reviewed_at}`. A rejection is followed by a fresh `requirement.due` for the\n  same document, with a new deadline and the `reason`. Not emitted while a document is\n  waiting on a human reviewer.\n- `wallet_policy.updated` - emitted when a change to a managed wallet's policy is\n  decided: `{wallet_id, change: {id, kind: destination.add|limits.update, status, ...}}`.\n  For a destination, `status: applied` means it is already in `destinations.allowed`.\n  For limits, `status: approved` means accepted and not yet in force - the signed record\n  is issued minutes later; confirm with `GET /wallets/{wallet_id}/policy`\n  (`limits.valid_until`). `rejected` carries a `reason` when one was given. Not emitted\n  when you PATCH: requesting is not changing.\n- `transfer.approval_required` - not emitted: no send waits on approver keys.\n- `limit_request.updated` - emitted when the public status of a limit increase request\n  (`POST /customers/{customer_id}/limit-requests`) changes: `awaiting_documents`,\n  `in_review`, `approved` (with `approved_limit`) or `declined`. `data` is the\n  `LimitRequest` itself. Once per status. A decline carries no reason.\n- `agent_action.updated` - emitted when the public status of an agent's payment or payout\n  (`POST /agents/{agent_id}/payments`, `/payouts` or `/fundings`) changes, to both the\n  agent platform and the company whose money it is. `data` is the `AgentAction` itself. Once\n  per status. `authorized` after `awaiting_approval` means a person at the company approved: a payout then runs on its own, and for a payment you repeat\n  the request with the same `idempotency_key` to receive the signature. `settled`\n  means the token contract shows the authorization used. `returned` means a payout's transfer ended\n  without delivering the money (`cancelled` or `expired`): the mandate's budget is released.\n  A payout becomes `settled` when its transfer completes. A transfer that needs review (including a Pix return) or that failed\n  stays `executed` until OZAV reviews it; the event is still sent when the receipt's `transfer_status` changes.\n  A funding (`POST /agents/{agent_id}/fundings`) is announced `awaiting_payment` when its Pix charge exists, then\n  `settled` when the company paid and the stablecoin arrived, or `returned` if the charge expired unpaid. A refusal carries the mandate's reasons\n  only; compliance refusals carry no reason. Events are not guaranteed to arrive in order, and a\n  status that changes twice before OZAV reads it (for example `authorized` then `signed` in one\n  request) is announced once, as the status it has then: `GET /agent-actions/{action_id}` is the\n  source of truth. An action waiting for approval that nobody decides ends as `expired`.\n  When an exception case (dispute) on the action is opened or changes status (`open`,\n  `under_review`, `resolved_refunded`, `resolved_denied`), the same event goes out with the case\n  in `data.exception`, once per case status.\n",
        "enum": [
          "webhook.test",
          "customer.created",
          "customer.updated",
          "verification.created",
          "verification.updated",
          "screening.completed",
          "transfer.updated",
          "transfer.completed",
          "transfer.failed",
          "card.created",
          "card.updated",
          "card.fraud_confirmed",
          "card.fraud_case_closed",
          "card.authorized",
          "card.settled",
          "card.declined",
          "card.refunded",
          "card.reversed",
          "account.updated",
          "account.credited",
          "deposit.credited",
          "requirement.due",
          "document.reviewed",
          "wallet_policy.updated",
          "transfer.approval_required",
          "limit_request.updated",
          "agent_action.updated"
        ]
      },
      "WebhookEndpointCreateRequest": {
        "type": "object",
        "required": [
          "url",
          "enabled_events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "HTTPS URL that will receive signed POST deliveries."
          },
          "enabled_events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "description": "Event types to deliver to this endpoint."
          },
          "description": {
            "type": "string",
            "maxLength": 255,
            "description": "Optional human-readable label for the endpoint."
          }
        }
      },
      "WebhookEndpointUpdateRequest": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Replacement HTTPS delivery URL."
          },
          "enabled_events": {
            "type": "array",
            "minItems": 1,
            "description": "Replaces the subscription list; it is not merged with the current one. Send the full set you want, including the types you already had, or you will silently unsubscribe from them.",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "status": {
            "type": "string",
            "description": "Set to `disabled` to stop deliveries without deleting the endpoint (the signing secret and the subscription list survive), or back to `active` to resume.",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 255,
            "description": "Replacement human label. Not used for routing."
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "description": "Webhook endpoint configuration. Secrets, secret hashes, Vault refs, and internal ids are never returned.",
        "required": [
          "id",
          "object",
          "url",
          "enabled_events",
          "status",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque webhook endpoint identifier, prefixed `ozav_wh_`."
          },
          "object": {
            "type": "string",
            "const": "webhook_endpoint",
            "description": "Resource type discriminator; always the literal `webhook_endpoint`."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The HTTPS URL that receives the deliveries. Verify the signature on every request to it (see the Webhooks section); an unverified receiver accepts forged events."
          },
          "enabled_events": {
            "type": "array",
            "description": "The event types this endpoint is subscribed to. An event whose type is not listed here is never delivered to this endpoint, and no record of a skipped delivery is created.",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "status": {
            "type": "string",
            "description": "`active` - deliveries are attempted. `disabled` - nothing is delivered and nothing is\nqueued for later; events that occur while disabled are not replayed on re-enable.\n\nOZAV does not disable an endpoint on its own. A URL that has failed every delivery for\nweeks still reads `active`, so `status` alone is not a health signal - read `health`.\n",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-form label you set. Never used for routing or delivery."
          },
          "health": {
            "type": "object",
            "description": "Read-only delivery health. `last_success_at: null` on an endpoint that has existed for\na while means it has never delivered, which is the failure that otherwise stays\ninvisible for weeks. Absent when the aggregate could not be read; its absence never\nfails the request.\n\n`last_error` separates the two opposite causes, because the fix differs: an `HTTP 401`\nmeans your server answered and refused (a signature problem), while a transport error\nsuch as `dns error: ...` means nothing answered at all (hostname, TLS, firewall) and no\nsignature change will help.\n",
            "properties": {
              "last_success_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When a delivery to this endpoint last got a 2xx. `null` means never."
              },
              "last_failure_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When a delivery to this endpoint last failed (non-2xx or transport error). `null` means no failure has been recorded."
              },
              "last_error": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The most recent failure, verbatim - either the HTTP status your server returned or the transport-level error. `null` when nothing has failed."
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the endpoint was registered (RFC 3339, UTC)."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the endpoint was last modified (RFC 3339, UTC)."
          }
        }
      },
      "WebhookEndpointWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "Signing secret (`ozav_whsec_...`); returned only on creation and rotation."
              }
            }
          }
        ]
      },
      "WebhookDeliveryStatus": {
        "type": "string",
        "enum": [
          "pending",
          "delivering",
          "succeeded",
          "failed",
          "exhausted"
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "description": "One delivery attempt stream for one endpoint/event pair. OZAV retries non-2xx\nand network failures with backoff until the delivery succeeds or exhausts its\nattempt budget. Response bodies are stored only as truncated previews.\n",
        "required": [
          "id",
          "object",
          "endpoint_id",
          "event_id",
          "event_type",
          "status",
          "attempt_count",
          "next_attempt_at",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque delivery identifier, prefixed `ozav_wd_`."
          },
          "object": {
            "type": "string",
            "const": "webhook_delivery",
            "description": "Resource type discriminator; always the literal `webhook_delivery`."
          },
          "endpoint_id": {
            "type": "string",
            "description": "The `ozav_wh_` endpoint this delivery targets."
          },
          "event_id": {
            "type": "string",
            "description": "The `ozav_evt_` event being delivered."
          },
          "event_type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "status": {
            "$ref": "#/components/schemas/WebhookDeliveryStatus"
          },
          "attempt_count": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of delivery attempts made so far."
          },
          "next_attempt_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the next retry is scheduled (RFC 3339, UTC). It keeps moving while the backoff runs; once `status` is `succeeded` or `exhausted` it stops being meaningful."
          },
          "last_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the most recent attempt was made. `null` before the first attempt."
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When an attempt got a 2xx. `null` on a delivery that has never succeeded - which, on an endpoint whose `response_status` is also `null`, means nothing at the URL ever answered."
          },
          "response_status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Last HTTP status code returned by your endpoint, if a response was received."
          },
          "response_preview": {
            "type": [
              "string",
              "null"
            ],
            "description": "Truncated preview of the last response body."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Last network/timeout error, if no HTTP response was received."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the delivery row was created (RFC 3339, UTC) - effectively when the event was queued for this endpoint."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the delivery row last changed (RFC 3339, UTC) - every attempt moves it. }"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "object",
          "type",
          "created_at",
          "data"
        ],
        "description": "Canonical event envelope POSTed to webhook endpoints. OZAV signs the exact\nJSON body bytes with HMAC-SHA256. Deliveries include:\n- `X-OZAV-Signature: t=<unix>,v1=<hex>`, where `v1` is HMAC-SHA256 over\n  `\"<t>.<raw body>\"`, keyed with your endpoint secret **exactly as returned,\n  including the `ozav_whsec_` prefix**. The prefix is part of the key, not a\n  display label: stripping it produces a completely different digest and every\n  delivery fails verification. `v1` is lowercase hex; parse it out of the header\n  rather than comparing the whole `t=...,v1=...` value.\n- `X-OZAV-Event-ID`, stable across retries for idempotency.\n- `X-OZAV-Delivery-ID`, unique to this endpoint/event delivery row.\n\nReject signatures whose timestamp is outside your tolerance window (five\nminutes is recommended), and dedupe by `X-OZAV-Event-ID`.\n",
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque event identifier, prefixed `ozav_evt_`."
          },
          "object": {
            "type": "string",
            "const": "event",
            "description": "Resource type discriminator; always the literal `event`."
          },
          "type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "livemode": {
            "type": "boolean",
            "description": "`true` for an event about live resources, `false` for one from the sandbox. An\nendpoint created in the dashboard before it asked for an environment receives both, so\nbranch on this field rather than on how the endpoint was registered. `GET /events` always returns it. A webhook delivery\ncarries it on every event emitted since the field was introduced; a retry of an older\nevent can arrive without it. When it is missing, look the event up with\n`GET /events/{event_id}`: each key finds only the events of its own environment. An\nendpoint registered with an API key only ever receives events of that key's environment.\nBefore this field was introduced, the `customer.updated` OZAV sends on its own after a\nreview (a status change, a new limit or a request for information), `wallet_policy.updated` about a wallet with only a\nsandbox id, and `account.updated` about a sandbox account could be recorded as\nproduction; such an older event keeps `livemode: true`.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event occurred (RFC 3339, UTC), not when this delivery attempt was made. It stays the same across retries - use it, not receipt time, to order events."
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "The affected resource, in the same shape its `GET` returns. Events about transfers,\ncustomers, cards, verifications and screenings carry it under the resource's own\nname: `data.transfer`, `data.customer`, `data.card`, `data.verification`,\n`data.screening`. Other events document their own `data` in the event list.\n\nIt is a snapshot at event time, and it can be stale by the time you read it. When the\ncurrent state matters (a decision about money), re-fetch by id rather than acting on\nthis body. Deliveries can also arrive out of order.\n"
          }
        }
      },
      "Page": {
        "type": "object",
        "description": "The uniform envelope every list endpoint returns.",
        "required": [
          "data",
          "has_more"
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "The page of results. Each list endpoint documents its own item type.",
            "items": {}
          },
          "has_more": {
            "type": "boolean",
            "description": "True when more results exist beyond this page - keep paging with `starting_after`."
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque cursor for the next page; pass it as `starting_after`. Null on the last page."
          }
        }
      },
      "ErrorDetail": {
        "type": "object",
        "description": "A single field-level error, when the top-level error decomposes into several.",
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable code for this specific field error."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation of this field error."
          },
          "param": {
            "type": "string",
            "description": "The request field this error refers to."
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "The envelope every error response uses, alongside the HTTP status code.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "description": "The error payload; always present on a non-2xx response.",
            "required": [
              "type",
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "description": "Stable high-level error class (see the \"Errors\" section for the full table):\n- `invalid_request_error` - malformed or unfulfillable input.\n- `authentication_error` - missing, invalid, revoked, or expired key.\n- `permission_error` - authenticated but not permitted (entitlement or scope).\n- `not_found_error` - the resource does not exist or is not visible.\n- `conflict_error` - conflicts with existing state (e.g. idempotency-key reuse).\n- `rate_limit_error` - too many requests; honor `Retry-After`.\n- `idempotency_error` - the idempotency layer was unreachable; retry.\n- `api_error` - an unexpected server-side or dependency error.\n",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "permission_error",
                  "not_found_error",
                  "conflict_error",
                  "rate_limit_error",
                  "idempotency_error",
                  "api_error"
                ]
              },
              "code": {
                "type": "string",
                "description": "Stable machine-readable code. Per-operation codes include ENTITLEMENT_REQUIRED,\nINSUFFICIENT_SCOPE, IDEMPOTENCY_KEY_REUSED. Platform-wide codes any operation may\nreturn: API_V2_DISABLED (503, kill switch off), AUTH_UNAVAILABLE and\nLOOKUP_UNAVAILABLE (503, fail-closed on a dependency error), and, where an\noperation reaches a provisioning path outside your entitlements,\nPROVISIONING_NOT_IMPLEMENTED and\nVERIFICATION_SESSION_NOT_IMPLEMENTED (501).\n"
              },
              "message": {
                "type": "string",
                "description": "Human-readable description of what went wrong."
              },
              "param": {
                "type": "string",
                "description": "The request field the error refers to, when applicable."
              },
              "request_id": {
                "type": "string",
                "description": "Identifier of this exact request - include it in every support request."
              },
              "details": {
                "description": "Usually an array of error detail objects (`{code, message,\nparam}`). The router's 404 catch-all instead returns a\ndiagnostic object variant:\n`{\"diag\": {\"method\": \"...\", \"pathParts\": [\"...\"]}}`.\n",
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ErrorDetail"
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "diag": {
                        "type": "object",
                        "description": "Router 404 diagnostic - the parsed method and path segments of the unmatched request.",
                        "properties": {
                          "method": {
                            "type": "string",
                            "description": "The HTTP method of the unmatched request."
                          },
                          "pathParts": {
                            "type": "array",
                            "description": "The path segments the router parsed from the unmatched request.",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      },
      "Hex32": {
        "type": "string",
        "pattern": "^[0-9a-f]{64}$",
        "description": "32 bytes, lowercase hex."
      },
      "VaultRootRequest": {
        "type": "object",
        "required": [
          "administrators",
          "threshold"
        ],
        "properties": {
          "administrators": {
            "type": "array",
            "minItems": 1,
            "maxItems": 64,
            "items": {
              "$ref": "#/components/schemas/Hex32"
            },
            "description": "Ed25519 public keys of your administrators."
          },
          "threshold": {
            "type": "integer",
            "minimum": 1,
            "maximum": 64,
            "description": "How many administrators must sign. Never more than the number of administrators."
          }
        }
      },
      "VaultRoot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`ozav_vrt_...`"
          },
          "object": {
            "type": "string",
            "enum": [
              "vault_root"
            ]
          },
          "administrators": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Hex32"
            }
          },
          "threshold": {
            "type": "integer"
          },
          "commitment": {
            "$ref": "#/components/schemas/Hex32"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VaultAsset": {
        "type": "object",
        "required": [
          "family",
          "id"
        ],
        "properties": {
          "family": {
            "type": "string",
            "enum": [
              "evm",
              "solana",
              "stellar",
              "tron"
            ]
          },
          "id": {
            "type": "string",
            "description": "Asset id in hex: token contract (evm, tron: 20 bytes), mint (solana: 32 bytes)."
          }
        }
      },
      "VaultGrantRequest": {
        "type": "object",
        "required": [
          "approvers",
          "threshold",
          "operator_cap",
          "cap_asset",
          "valid_until",
          "signatures"
        ],
        "properties": {
          "approvers": {
            "type": "array",
            "minItems": 1,
            "maxItems": 32,
            "items": {
              "type": "object",
              "required": [
                "type",
                "public",
                "role"
              ],
              "properties": {
                "type": {
                  "type": "integer",
                  "enum": [
                    1,
                    2,
                    3
                  ],
                  "description": "1 passkey, 2 Ed25519, 3 P-256."
                },
                "public": {
                  "type": "string",
                  "description": "Hex: 32 bytes for Ed25519, 65 (uncompressed) for P-256 and passkey."
                },
                "role": {
                  "type": "integer",
                  "enum": [
                    1,
                    2,
                    3
                  ],
                  "description": "1 operator (limited by `operator_cap`), 2 approver, 3 administrator."
                },
                "raw_key_material": {
                  "type": "boolean",
                  "description": "Only an administrator with a raw key may move key material."
                },
                "rp_id": {
                  "type": "string",
                  "description": "Passkey only: your own domain."
                },
                "origins": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Passkey only: https pages of the `rp_id` domain, up to 8."
                }
              }
            }
          },
          "threshold": {
            "type": "integer",
            "minimum": 1
          },
          "operator_cap": {
            "type": "string",
            "description": "Positive integer in decimal text, up to 128 bits, in the smallest unit of `cap_asset`."
          },
          "cap_asset": {
            "type": "object",
            "required": [
              "family",
              "network",
              "id"
            ],
            "properties": {
              "family": {
                "type": "string",
                "enum": [
                  "evm",
                  "solana",
                  "stellar",
                  "tron"
                ]
              },
              "network": {
                "type": "integer",
                "description": "EVM chain id; 0 on the other families."
              },
              "id": {
                "type": "string",
                "description": "Asset id in hex."
              }
            }
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "ISO-8601 UTC ending in Z."
          },
          "signatures": {
            "type": "array",
            "description": "Administrator signatures over the grant message.",
            "items": {
              "type": "object",
              "required": [
                "public",
                "signature"
              ],
              "properties": {
                "public": {
                  "$ref": "#/components/schemas/Hex32"
                },
                "signature": {
                  "type": "string",
                  "description": "64 bytes, hex."
                }
              }
            }
          }
        }
      },
      "VaultGrant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`ozav_vgr_...`"
          },
          "object": {
            "type": "string",
            "enum": [
              "vault_grant"
            ]
          },
          "root_id": {
            "type": "string"
          },
          "commitment": {
            "$ref": "#/components/schemas/Hex32"
          },
          "valid_until": {
            "type": "string",
            "format": "date-time"
          },
          "signatures": {
            "type": "integer",
            "description": "Valid administrator signatures."
          },
          "approvers": {
            "type": "integer"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the grant was revoked; null while it is valid."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VaultRequest": {
        "type": "object",
        "required": [
          "root_id",
          "rules",
          "share_2_destination",
          "share_3_destination",
          "root_authorization"
        ],
        "properties": {
          "root_id": {
            "type": "string"
          },
          "rules": {
            "type": "object",
            "required": [
              "destinations"
            ],
            "properties": {
              "destinations": {
                "type": "array",
                "minItems": 1,
                "maxItems": 64,
                "items": {
                  "type": "object",
                  "required": [
                    "family",
                    "address"
                  ],
                  "properties": {
                    "family": {
                      "type": "string",
                      "enum": [
                        "evm",
                        "solana",
                        "stellar",
                        "tron"
                      ]
                    },
                    "address": {
                      "type": "string",
                      "description": "Hex: 20 bytes on evm and tron, 32 on solana and stellar."
                    }
                  }
                }
              },
              "per_send_cap": {
                "type": "string",
                "description": "Positive decimal text. Goes with `cap_asset`."
              },
              "cap_asset": {
                "$ref": "#/components/schemas/VaultAsset"
              },
              "exportable": {
                "type": "boolean",
                "description": "Allow a recovery kit to be exported."
              }
            }
          },
          "share_2_destination": {
            "type": "object",
            "required": [
              "spki"
            ],
            "properties": {
              "spki": {
                "type": "string",
                "description": "DER public key (hex) that receives key share 2."
              }
            }
          },
          "share_3_destination": {
            "type": "object",
            "required": [
              "spki"
            ],
            "properties": {
              "spki": {
                "type": "string",
                "description": "DER public key (hex) that receives key share 3. Different from share 2."
              }
            }
          },
          "root_authorization": {
            "type": "object",
            "required": [
              "valid_until",
              "signatures"
            ],
            "properties": {
              "valid_until": {
                "type": "string",
                "format": "date-time"
              },
              "signatures": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "public",
                    "signature"
                  ],
                  "properties": {
                    "public": {
                      "$ref": "#/components/schemas/Hex32"
                    },
                    "signature": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Vault": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`ozav_vlt_...`"
          },
          "object": {
            "type": "string",
            "enum": [
              "vault"
            ]
          },
          "root_id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "active",
              "failed"
            ]
          },
          "lineage": {
            "type": "string",
            "description": "Active only. Identifies the vault in the enclave (16 bytes, hex)."
          },
          "addresses": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Active only."
          },
          "receipt": {
            "type": "object",
            "description": "Active only. The enclave's signed answer and the sealed key shares for your destinations. Verify before depositing."
          },
          "activated_at": {
            "type": "string",
            "format": "date-time"
          },
          "failure_code": {
            "type": "string",
            "description": "Failed only."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VaultDestinationGrantRequest": {
        "type": "object",
        "required": [
          "destinations",
          "valid_until",
          "signatures"
        ],
        "properties": {
          "destinations": {
            "type": "array",
            "minItems": 1,
            "maxItems": 64,
            "items": {
              "type": "object",
              "required": [
                "family",
                "address"
              ],
              "properties": {
                "family": {
                  "type": "string",
                  "enum": [
                    "evm",
                    "solana",
                    "stellar",
                    "tron"
                  ]
                },
                "address": {
                  "type": "string",
                  "description": "Hex: 20 bytes on evm and tron, 32 on solana and stellar."
                }
              }
            }
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "ISO-8601 UTC ending in Z. The grant stops permitting these destinations then."
          },
          "signatures": {
            "type": "array",
            "description": "Administrator signatures over the destination grant message (vault, sealed rules, destinations, expiry).",
            "items": {
              "type": "object",
              "required": [
                "public",
                "signature"
              ],
              "properties": {
                "public": {
                  "$ref": "#/components/schemas/Hex32"
                },
                "signature": {
                  "type": "string",
                  "description": "64 bytes, hex."
                }
              }
            }
          }
        }
      },
      "VaultDestinationGrant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`ozav_vdg_...`"
          },
          "object": {
            "type": "string",
            "enum": [
              "vault_destination_grant"
            ]
          },
          "vault_id": {
            "type": "string"
          },
          "destinations": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "message": {
            "$ref": "#/components/schemas/Hex32"
          },
          "valid_until": {
            "type": "string",
            "format": "date-time"
          },
          "signatures": {
            "type": "integer",
            "description": "Valid administrator signatures."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VaultAttestation": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "vault_attestation"
            ]
          },
          "document": {
            "type": "string",
            "description": "The COSE_Sign1 attestation document, lowercase hex. Verify it yourself."
          },
          "observed_at": {
            "type": "string",
            "format": "date-time",
            "description": "When OZAV received it. Not proof of freshness."
          }
        }
      },
      "VaultTransactionRequest": {
        "type": "object",
        "required": [
          "grant_id",
          "family",
          "derivation_path",
          "raw_transaction",
          "intent",
          "user_envelope",
          "approve_until"
        ],
        "properties": {
          "grant_id": {
            "type": "string",
            "description": "`ozav_vgr_...` of a grant of the vault's root."
          },
          "destination_grant_id": {
            "type": "string",
            "description": "Optional. `ozav_vdg_...` of a destination grant of this vault."
          },
          "family": {
            "type": "string",
            "enum": [
              "evm",
              "solana",
              "tron"
            ]
          },
          "derivation_path": {
            "type": "string",
            "example": "m/44'/60'/0'/0/0"
          },
          "raw_transaction": {
            "type": "string",
            "description": "The exact bytes the vault will sign, hex, up to 64 KiB."
          },
          "intent": {
            "description": "What the bytes do. The enclave checks it against the bytes and the vault rules.",
            "oneOf": [
              {
                "type": "object",
                "title": "evm",
                "required": [
                  "chain_id",
                  "token",
                  "destination",
                  "amount",
                  "fee_cap_wei"
                ],
                "properties": {
                  "chain_id": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "token": {
                    "type": "string",
                    "description": "20 bytes, hex."
                  },
                  "destination": {
                    "type": "string",
                    "description": "20 bytes, hex."
                  },
                  "amount": {
                    "type": "string",
                    "description": "Positive integer in decimal text, smallest unit."
                  },
                  "fee_cap_wei": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "object",
                "title": "tron",
                "required": [
                  "token",
                  "destination",
                  "amount",
                  "fee_cap_sun"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "destination": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "string"
                  },
                  "fee_cap_sun": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "object",
                "title": "solana",
                "required": [
                  "mint",
                  "destination_owner",
                  "amount"
                ],
                "properties": {
                  "mint": {
                    "type": "string",
                    "description": "32 bytes, hex."
                  },
                  "destination_owner": {
                    "type": "string",
                    "description": "32 bytes, hex."
                  },
                  "amount": {
                    "type": "string"
                  }
                }
              }
            ]
          },
          "user_envelope": {
            "type": "object",
            "description": "Your key share 3, sealed to your KMS key. Passed to the enclave, never opened by OZAV."
          },
          "approve_until": {
            "type": "string",
            "format": "date-time",
            "description": "ISO-8601 UTC ending in Z, up to 7 days ahead."
          }
        }
      },
      "VaultTransaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`ozav_vtx_...`"
          },
          "object": {
            "type": "string",
            "enum": [
              "vault_transaction"
            ]
          },
          "vault_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "awaiting_approvals",
              "signed",
              "rejected",
              "submitted",
              "expired"
            ]
          },
          "grant_id": {
            "type": "string"
          },
          "challenge": {
            "$ref": "#/components/schemas/Hex32"
          },
          "approve_until": {
            "type": "string",
            "format": "date-time"
          },
          "request": {
            "type": "object",
            "description": "The transaction as stored, with the intent normalized."
          },
          "approvals": {
            "type": "object",
            "properties": {
              "required": {
                "type": "integer"
              },
              "received": {
                "type": "integer"
              }
            }
          },
          "signing": {
            "type": "boolean",
            "description": "True while the quorum is met and the enclave has not answered."
          },
          "signed_transaction": {
            "type": "string",
            "description": "Signed status only. Hex. Broadcast it yourself."
          },
          "failure_code": {
            "type": "string",
            "description": "Rejected status only."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "VaultApprovalRequest": {
        "type": "object",
        "required": [
          "type",
          "public",
          "signature",
          "valid_until"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "ed25519",
              "p256",
              "passkey"
            ]
          },
          "public": {
            "type": "string",
            "description": "Hex: 32 bytes for ed25519, 65 for p256 and passkey."
          },
          "signature": {
            "type": "string",
            "description": "Hex. Ed25519: 64 bytes. P-256 and passkey: DER."
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "The transaction's `approve_until`."
          },
          "auth_data": {
            "type": "string",
            "description": "Passkey only, hex."
          },
          "client_data_json": {
            "type": "string",
            "description": "Passkey only, hex."
          },
          "authenticator_counter": {
            "type": "integer",
            "description": "Passkey only."
          }
        }
      }
    }
  }
}