{
  "openapi": "3.1.1",
  "info": {
    "title": "Volume API",
    "version": "0.1.0",
    "summary": "Hosted crypto checkout and invoices for businesses.",
    "description": "Volume lets a business take payment in crypto. Create an invoice, open a hosted\ncheckout session for it, and Volume issues a deposit address, credits the payment\nonce its custodian has accepted it, and tells you with a signed webhook.\n\nThis specification is the contract: the API's routes and responses are tested\nagainst it.\n\n## Test and live\n\nThe API key selects the mode: `vk_test_…` payments are simulated and `vk_live_…`\nones are real. Test and live data, endpoints and events are fully isolated. Pay a\ntest checkout from its page, or with\n`POST /v1/test_helpers/checkout_sessions/{id}/pay`, and the same events follow as\nfor a real payment.\n\n## Idempotency\n\n`POST /v1/api_keys`, `POST /v1/invoices`, `POST /v1/checkout_sessions` and\n`POST /v1/payouts` take an `Idempotency-Key`. Retrying with the same key replays the original response, marked\n`Idempotent-Replayed: true`, instead of doing it twice. The API key replay leaves\nout the key itself, which is shown once and stored nowhere.\n\n## Webhooks\n\nRegister an endpoint per mode (`POST /v1/webhook_endpoints?mode=`) and keep its\n`whsec_…` secret. Volume POSTs each event as JSON (the `Event` schema) and signs\nit:\n\n```\nVolume-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">\n```\n\nVerify it against the raw body before parsing, and refuse a timestamp more than\nfive minutes off:\n\n```js\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport function verifySignature(secret, rawBody, header, now = Date.now()) {\n  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));\n  const t = Number(parts.t);\n  const expected = createHmac('sha256', secret)\n    .update(`${t}.${rawBody}`)\n    .digest('hex');\n  const given = Buffer.from(parts.v1 ?? '');\n  const valid =\n    given.length === expected.length &&\n    timingSafeEqual(given, Buffer.from(expected));\n  return valid && Math.abs(now / 1000 - t) <= 300;\n}\n```\n\nAnswer `2xx` within ten seconds; anything else, or no answer, is tried again with\nbackoff for three days. Delivery is at least once and not in order: dedupe on the\nevent `id`, and read the state you need from the event, or fetch it. Grant access\non `invoice.paid`. `POST /v1/webhook_endpoints/{id}/test` sends a test event.\n\n## Payouts\n\nEvery payment credits the merchant's balance, in dollars, less Volume's fee. With\nauto-payout on (the default), each one is then paid out to the default destination:\nfor now, a Nigerian bank account, in naira. Destinations and payout settings are\nmanaged while logged in, never with an API key, so a leaked key cannot send money\nanywhere new.\n\nA bank account is added by bank code (`GET /v1/banks`) and account number; its\nholder's name is read from the bank. In live mode it must match the business's\nverified legal name. A merchant's first destination can be paid at once. Any later\none only 48 hours after it is added, Saturdays and Sundays in Lagos not counted,\nand every user of the account is emailed when one is added.\n\n## Rate limits\n\nRequests are counted per credential, and per address where there is none. Logging\nin, signing up and verifying codes have tight limits. A refusal is\n`429 too_many_requests` with a `Retry-After` header.\n\n## Errors\n\nEvery error response is `{ \"error\": { \"type\", \"code\", \"message\", \"request_id\" } }`.\nThe `type` is a stable category; `code` is the specific reason.\n\n## Request ids\n\nEvery response carries a `Request-Id` header, which also appears as `request_id` in\nerror bodies. Quote it when reporting a problem.\n"
  },
  "servers": [
    {
      "url": "https://api.paywithvolume.com",
      "description": "Production. Mode comes from the API key, not the URL."
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "The merchant a login or API key acts for."
    },
    {
      "name": "Webhooks",
      "description": "Where Volume delivers a merchant's events, signed; see \"Webhooks\" above."
    },
    {
      "name": "Payments",
      "description": "Invoices, and the hosted checkouts that collect them."
    },
    {
      "name": "Test helpers",
      "description": "Test mode only. Simulate what a payer does, for your own tests."
    }
  ],
  "paths": {
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "The account",
        "description": "The merchant a session or API key acts for, and its status.",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/webhook_endpoints": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "summary": "List webhook endpoints",
        "description": "The endpoints registered in this mode, deleted ones aside, newest first.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The endpoints.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    }
                  },
                  "required": [
                    "object",
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "summary": "Register a webhook endpoint",
        "description": "Registers the https URL that receives events in one mode, and returns its\nsigning secret this once. Deliveries are signed\n`Volume-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\">`. A\nlive endpoint needs an active account. One registration per URL.\n\nWith an API key, in the key's mode: an integration can register its own\nendpoint when it deploys. Logged in, name the mode.\n",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "enabled_events": {
                    "type": "array",
                    "description": "Event types to deliver, or `['*']` (the default) for all.",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The endpoint, with its signing secret this once.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/webhook_endpoints/{webhook_endpoint_id}": {
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "summary": "Delete a webhook endpoint",
        "description": "Nothing more is sent to it, and deliveries waiting to retry are cancelled.\nAnswers with the endpoint, its status `deleted`; it is not found after that.\n",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_endpoint_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint, deleted.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/webhook_endpoints/{webhook_endpoint_id}/test": {
      "post": {
        "operationId": "sendTestWebhook",
        "summary": "Send a test event",
        "description": "Sends the endpoint a `webhook_endpoint.test` event, whatever it subscribes to,\nsigned and delivered like any other. The answer is the event; its delivery is\nqueued and follows within seconds.\n",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_endpoint_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The test event, queued for delivery.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          },
          "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"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/webhook_deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "List webhook deliveries",
        "description": "Every event sent to the account's endpoints in this mode and how each endpoint\nanswered, newest first, to debug an integration.\n",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_endpoint",
            "in": "query",
            "required": false,
            "description": "Only deliveries to this endpoint.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "succeeded",
                "failed",
                "cancelled"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageCursor"
          }
        ],
        "responses": {
          "200": {
            "description": "The deliveries.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/invoices": {
      "post": {
        "operationId": "createInvoice",
        "summary": "Create an invoice",
        "description": "A one-off USD invoice, in the mode of the API key. Open a checkout session for it\nto collect payment. At most $100,000.00. Lightning carries at most\n$1,000.00, so a checkout above that does not offer it.\n",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "string",
                    "pattern": "^(0|[1-9][0-9]{0,8})(\\.[0-9]{1,2})?$",
                    "description": "USD as a decimal string, such as `25.00`."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "customer_email": {
                    "type": "string",
                    "format": "email",
                    "description": "The payer, when the invoice is for someone in particular."
                  },
                  "metadata": {
                    "$ref": "#/components/schemas/Metadata"
                  }
                },
                "required": [
                  "amount"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The invoice.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/invoices/{invoice_id}": {
      "get": {
        "operationId": "getInvoice",
        "summary": "Get an invoice",
        "description": "The invoice, its status, and every transfer that paid towards it.",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "invoice_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/checkout_sessions": {
      "get": {
        "operationId": "listCheckoutSessions",
        "summary": "List checkout sessions",
        "description": "The account's checkouts, newest first. With an API key, those of its mode; when\nlogged in, name the mode.\n",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CheckoutSessionStatus"
            }
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageCursor"
          }
        ],
        "responses": {
          "200": {
            "description": "The checkout sessions.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CheckoutSession"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "createCheckoutSession",
        "summary": "Create a checkout session",
        "description": "A hosted checkout for an open invoice. Send the payer to its `url`\n(`paywithvolume.com/{id}`); it is open for 24 hours, and once the payer picks a\ncoin they have 30 minutes to pay. Return URLs must be https in live mode.\n",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invoice_id": {
                    "type": "string"
                  },
                  "customer_email": {
                    "type": "string",
                    "format": "email",
                    "description": "Required unless the invoice names its customer."
                  },
                  "success_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "cancel_url": {
                    "type": "string",
                    "format": "uri"
                  }
                },
                "required": [
                  "invoice_id",
                  "success_url",
                  "cancel_url"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The checkout session.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                }
              }
            }
          },
          "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"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/checkout_sessions/{checkout_session_id}": {
      "get": {
        "operationId": "getCheckoutSession",
        "summary": "Get a checkout session",
        "description": "Its status, the payer's choice of coin and network, and what has arrived. With\nan API key, in its mode; when logged in, name the mode.\n",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "checkout_session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The checkout session.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/test_helpers/checkout_sessions/{checkout_session_id}/pay": {
      "post": {
        "operationId": "simulateCheckoutPayment",
        "summary": "Pay a test checkout by simulation",
        "description": "Test keys only. Once the payer has chosen a coin and network, a simulated\npayment arrives at the checkout's address at once and is accepted about ten\nseconds later, through the same path as a real one: the checkout's status,\n`payment.confirmed`, `invoice.paid` (with `invoice.overpaid` when over, or\n`invoice.underpaid` instead when short) and the merchant's balance all follow\nas they would live. `exact` sends what is still due, `short` a tenth less and\n`over` a tenth more, both past the tolerance (up to 1% or $0.50 short,\nwhichever is smaller, still counts as paid). A Lightning invoice is paid\nexactly only. No money moves. A live key is refused with `403 test_mode_only`.\n",
        "tags": [
          "Test helpers"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CheckoutSessionId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestPaymentRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The payment is on its way; the checkout as it stands.",
            "headers": {
              "Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutSession"
                }
              }
            }
          },
          "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"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "webhooks": {
    "event": {
      "post": {
        "operationId": "receiveEvent",
        "summary": "An event, delivered to your endpoint",
        "description": "Each event is POSTed to every active endpoint of yours, in its mode, that\nsubscribes to its type. Verify `Volume-Signature` first (see \"Webhooks\"), answer\n`2xx` within ten seconds, and dedupe on `id`. Anything but `2xx` is tried again\nwith backoff for three days.\n",
        "parameters": [
          {
            "name": "Volume-Signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Volume-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Volume-Event-Type",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any `2xx` means received."
          }
        },
        "tags": [
          "Webhooks"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A merchant API key, sent as `Authorization: Bearer vk_live_…`. The prefix\nselects the mode: `vk_test_` for testnets, `vk_live_` for real money.\n"
      }
    },
    "headers": {
      "RequestId": {
        "description": "Unique id for this request, for support and log correlation.",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^req_[0-9a-f]{32}$"
        }
      },
      "IdempotentReplayed": {
        "description": "Present, with `true`, when this is a replay of an earlier request.",
        "required": false,
        "schema": {
          "const": "true"
        }
      },
      "RetryAfter": {
        "description": "Whole seconds to wait before retrying, when waiting will help. A sign-up code\nthat has had too many wrong guesses has none: ask for a new code instead.\n",
        "required": false,
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Makes the request safe to retry: the same key replays the first response\ninstead of repeating the effect. At most 255 characters.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        }
      },
      "CheckoutSessionId": {
        "name": "checkout_session_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "PageLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "How many to return, newest first.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      },
      "PageCursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "The `next_cursor` of the page before, for the page after it.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "TestPaymentRequest": {
        "type": "object",
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "exact",
              "short",
              "over"
            ],
            "default": "exact",
            "description": "What the simulated payer sends: what is still due, a tenth less, or a tenth\nmore. Once nothing is due, `exact` pays it all again.\n"
          }
        },
        "additionalProperties": false
      },
      "CheckoutTheme": {
        "type": "string",
        "enum": [
          "dark",
          "light",
          "auto"
        ],
        "description": "How the hosted checkout looks, for every payer: `dark` (the default), `light`,\nor `auto`, which follows the payer's device.\n"
      },
      "Account": {
        "type": "object",
        "properties": {
          "object": {
            "const": "account"
          },
          "id": {
            "type": "string",
            "pattern": "^mer_[0-9A-Za-z]{24}$"
          },
          "business_name": {
            "type": "string"
          },
          "legal_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The registered name, set when Volume activates the account."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active",
              "suspended"
            ]
          },
          "live_access": {
            "type": "boolean",
            "description": "Whether live keys and live payments are open to this account."
          },
          "fee_bps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "Volume's fee on each payment credited to the account, in basis points (100\nis 1%), rounded down to the micro-dollar.\n"
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "checkout_theme": {
            "$ref": "#/components/schemas/CheckoutTheme"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "object",
          "id",
          "business_name",
          "legal_name",
          "status",
          "live_access",
          "fee_bps",
          "activated_at",
          "checkout_theme",
          "created_at"
        ],
        "additionalProperties": false
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "checkout.payment_detected",
          "payment.confirmed",
          "invoice.paid",
          "invoice.underpaid",
          "invoice.overpaid",
          "invoice.expired",
          "payout.sent",
          "payout.failed"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "object": {
            "const": "webhook_endpoint"
          },
          "id": {
            "type": "string",
            "pattern": "^whe_[0-9A-Za-z]{24}$"
          },
          "livemode": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "enabled_events": {
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/WebhookEventType"
                },
                {
                  "const": "*"
                }
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled",
              "deleted"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "secret": {
            "type": "string",
            "pattern": "^whsec_[A-Za-z0-9_-]{43}$",
            "description": "The signing secret. Only in the registration response."
          }
        },
        "required": [
          "object",
          "id",
          "livemode",
          "url",
          "description",
          "enabled_events",
          "status",
          "created_at"
        ],
        "additionalProperties": false
      },
      "WebhookDelivery": {
        "type": "object",
        "description": "One event on its way to one endpoint, and how the endpoint answered.",
        "properties": {
          "object": {
            "const": "webhook_delivery"
          },
          "id": {
            "type": "string",
            "pattern": "^whd_[0-9A-Za-z]{24}$"
          },
          "livemode": {
            "type": "boolean"
          },
          "event": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^evt_[0-9A-Za-z]{24}$"
              },
              "type": {
                "type": "string"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "id",
              "type",
              "created_at"
            ],
            "additionalProperties": false
          },
          "webhook_endpoint": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^whe_[0-9A-Za-z]{24}$"
              },
              "url": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "url"
            ],
            "additionalProperties": false
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed",
              "cancelled"
            ],
            "description": "`pending` until the endpoint answers 2xx (`succeeded`) or three days of\nretries run out (`failed`); `cancelled` when the endpoint was disabled or\ndeleted first.\n"
          },
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "last_status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What the endpoint last answered; null when nothing answered."
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "object",
          "id",
          "livemode",
          "event",
          "webhook_endpoint",
          "status",
          "attempts",
          "last_status_code",
          "last_error",
          "completed_at",
          "created_at"
        ],
        "additionalProperties": false
      },
      "Metadata": {
        "type": "object",
        "description": "Up to 20 keys of your own, each up to 40 characters, values up to 500.",
        "maxProperties": 20,
        "propertyNames": {
          "minLength": 1,
          "maxLength": 40
        },
        "additionalProperties": {
          "type": "string",
          "maxLength": 500
        }
      },
      "PaymentStatus": {
        "type": "string",
        "enum": [
          "detected",
          "confirmed",
          "dropped",
          "rejected"
        ],
        "description": "`confirmed` once the custodian has accepted it, and only then; `dropped` when a\nreorganisation removed it, `rejected` when the custodian refused it.\n"
      },
      "Payment": {
        "type": "object",
        "properties": {
          "object": {
            "const": "payment"
          },
          "id": {
            "type": "string",
            "pattern": "^pay_[0-9A-Za-z]{24}$"
          },
          "checkout_session": {
            "type": "string",
            "pattern": "^cs_[0-9A-Za-z]{24}$"
          },
          "asset": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC",
              "BTC"
            ]
          },
          "network": {
            "type": "string",
            "enum": [
              "tron",
              "bsc",
              "base",
              "solana",
              "bitcoin",
              "lightning"
            ]
          },
          "amount": {
            "type": "string",
            "description": "In the coin, as a decimal string."
          },
          "status": {
            "$ref": "#/components/schemas/PaymentStatus"
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null for a transfer from another account at the custodian."
          },
          "detected_at": {
            "type": "string",
            "format": "date-time"
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "object",
          "id",
          "checkout_session",
          "asset",
          "network",
          "amount",
          "status",
          "tx_hash",
          "detected_at",
          "confirmed_at"
        ],
        "additionalProperties": false
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "object": {
            "const": "invoice"
          },
          "id": {
            "type": "string",
            "pattern": "^inv_[0-9A-Za-z]{24}$"
          },
          "livemode": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "paid",
              "void"
            ]
          },
          "amount": {
            "type": "string",
            "description": "USD, as a decimal string."
          },
          "currency": {
            "const": "usd"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "$ref": "#/components/schemas/Metadata"
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "object",
          "id",
          "livemode",
          "status",
          "amount",
          "currency",
          "description",
          "customer",
          "customer_email",
          "metadata",
          "payments",
          "paid_at",
          "created_at"
        ],
        "additionalProperties": false
      },
      "PaymentChoice": {
        "type": "object",
        "description": "What the payer chose, and what they were asked to send.",
        "properties": {
          "source": {
            "type": "string",
            "description": "Where they pay from, such as `bybit`."
          },
          "asset": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC",
              "BTC"
            ]
          },
          "network": {
            "type": "string",
            "enum": [
              "tron",
              "bsc",
              "base",
              "solana",
              "bitcoin",
              "lightning"
            ]
          },
          "amount_due": {
            "type": "string",
            "description": "What must arrive, in the coin."
          },
          "withdraw_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "What an exchange payer withdraws, the amount due plus the exchange's fee."
          },
          "rate": {
            "type": "string",
            "description": "USD per whole coin when the address was issued."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "The end of the payer's 30 minutes."
          }
        },
        "required": [
          "source",
          "asset",
          "network",
          "amount_due",
          "withdraw_amount",
          "rate",
          "expires_at"
        ],
        "additionalProperties": false
      },
      "CheckoutSessionStatus": {
        "type": "string",
        "enum": [
          "open",
          "awaiting_payment",
          "detected",
          "confirming",
          "paid",
          "underpaid",
          "overpaid",
          "expired",
          "refund_due",
          "settled"
        ]
      },
      "CheckoutSession": {
        "type": "object",
        "properties": {
          "object": {
            "const": "checkout_session"
          },
          "id": {
            "type": "string",
            "pattern": "^cs_[0-9A-Za-z]{24}$"
          },
          "livemode": {
            "type": "boolean"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The hosted checkout, `paywithvolume.com/{id}`."
          },
          "status": {
            "$ref": "#/components/schemas/CheckoutSessionStatus"
          },
          "invoice": {
            "type": "string"
          },
          "customer": {
            "type": "string"
          },
          "customer_email": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "USD, as a decimal string."
          },
          "currency": {
            "const": "usd"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "The invoice's description, what is being paid for."
          },
          "payment": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PaymentChoice"
              },
              {
                "type": "null"
              }
            ]
          },
          "amount_received": {
            "type": [
              "string",
              "null"
            ],
            "description": "What has counted so far, in the chosen coin."
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "success_url": {
            "type": "string"
          },
          "cancel_url": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "object",
          "id",
          "livemode",
          "url",
          "status",
          "invoice",
          "customer",
          "customer_email",
          "amount",
          "currency",
          "description",
          "payment",
          "amount_received",
          "payments",
          "success_url",
          "cancel_url",
          "expires_at",
          "paid_at",
          "created_at"
        ],
        "additionalProperties": false
      },
      "Event": {
        "type": "object",
        "description": "What an endpoint receives, and the test event's answer. `data` holds the\nresources the event is about, as they were when it happened:\n`checkout_session` for every payment event, with `payment` for\n`checkout.payment_detected` and `payment.confirmed` (and `credited_usd`,\n`fee_usd` on the latter), `amount_received` with `amount_remaining` or\n`amount_excess` for `invoice.underpaid` and `invoice.overpaid`, and\n`webhook_endpoint` for `webhook_endpoint.test`.\n",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^evt_[0-9A-Za-z]{24}$",
            "description": "Unique per event; the same on every delivery of it. Dedupe on it."
          },
          "object": {
            "const": "event"
          },
          "type": {
            "type": "string",
            "description": "A `WebhookEventType`, or `webhook_endpoint.test`."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "livemode": {
            "type": "boolean"
          },
          "data": {
            "type": "object"
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "created_at",
          "livemode",
          "data"
        ],
        "additionalProperties": false
      },
      "ErrorType": {
        "type": "string",
        "description": "Stable error category.",
        "enum": [
          "invalid_request_error",
          "authentication_error",
          "payment_required",
          "permission_error",
          "not_found",
          "conflict",
          "unprocessable",
          "rate_limit_error",
          "api_error",
          "upstream_error"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "$ref": "#/components/schemas/ErrorType"
              },
              "code": {
                "type": "string",
                "description": "The specific reason, stable per `type`.",
                "examples": [
                  "not_found"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. Not for programmatic use."
              },
              "request_id": {
                "type": "string",
                "pattern": "^req_[0-9a-f]{32}$"
              },
              "retry_after": {
                "type": "integer",
                "description": "Seconds to wait before retrying, on `too_many_requests` and\n`too_many_attempts`. The same value as the `Retry-After` header.\n"
              }
            },
            "required": [
              "type",
              "code",
              "message",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request is malformed: `invalid_request`, `unknown_parameter`,\n`invalid_query_parameter`, `idempotency_key_required`,\n`invalid_idempotency_key`, `invalid_code`, `code_expired`,\n`signup_not_verified`, `invalid_webhook_url`, `unknown_event_type`,\n`invalid_amount`, `amount_too_large`, `invalid_url`,\n`customer_email_required`, `customer_mismatch`,\n`unsupported_payment_method`, `exact_amount_only`.\n",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No valid credential: `invalid_credentials`, `invalid_session`,\n`invalid_api_key`, `invalid_signature`.\n",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Not allowed for this account: `account_suspended` (a suspended account may\nonly read), `live_access_required` (live needs an active account),\n`test_mode_only` (a simulated payment in live mode), `not_permitted` (a staff\nmember's role does not allow it), `step_up_required` (an authenticator code is\nneeded first: `POST /v1/auth/step_up`, or `POST /admin/auth/step_up` for\nstaff), `authenticator_required` (a live money action needs an authenticator\nset up first: `POST /v1/auth/authenticator`).\n",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Clashes with what exists: `email_taken`, `webhook_endpoint_exists`,\n`idempotency_key_reuse_mismatch`, `invoice_not_open`,\n`checkout_session_closed`, `webhook_endpoint_disabled`,\n`legal_name_required`, `payout_destination_not_usable`,\n`payout_destination_required`, `insufficient_balance`, `balance_changed`,\n`no_payment_method`, `invoice_closed`, `staff_exists`, `not_on_yourself`\n(nobody changes their own role, status or authenticator), `last_super_admin`.\n",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Slow down: `too_many_requests` (a rate limit) or `too_many_attempts` (too many\nwrong codes or passwords).\n",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource, or not yours: `not_found`, `api_key_not_found`,\n`merchant_not_found`, `invoice_not_found`, `checkout_session_not_found`,\n`webhook_endpoint_not_found`, `payout_destination_not_found`,\n`payout_not_found`, `flag_not_found`, `staff_not_found`.\n",
        "headers": {
          "Request-Id": {
            "$ref": "#/components/headers/RequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
