{
  "openapi": "3.1.0",
  "info": {
    "title": "Crypto Pay API",
    "version": "2026-08-11",
    "summary": "Merchant payments API of the tgpay crypto wallet (@tgpaycryptobot).",
    "description": "Crypto Pay is the app-facing payments API of the tgpay crypto wallet:\ncreate invoices, refund them, send payouts and checks, run recurring\nsubscriptions, and receive signed webhooks.\n\nThe API is **Crypto Bot-compatible**: same method names, the same\n`{ok, result}` envelope, decimal-string amounts, the same webhook\nsignature scheme. An existing Crypto Bot integration can usually be\npointed at this API by changing only the base URL and the token.\nExtensions over the Crypto Bot contract are marked **Extension** in the\ndescriptions.\n\nHuman-readable documentation: https://docs.tgpaycrypto.com/crypto-pay-api-reference/\n\n## Conventions\n\n- **Authentication** — send your token in the `TgPayCrypto-API-Token`\n  header (canonical). `TgCryptoPay-API-Token` (the name before\n  2026-09-07) and `Crypto-Pay-API-Token` are accepted as compatibility\n  aliases; if several are sent, the canonical header wins.\n- **Envelope** — success is `{\"ok\": true, \"result\": …}` with HTTP 200;\n  errors are `{\"ok\": false, \"error\": {\"code\": <HTTP status>, \"name\":\n  \"<machine_name>\"}}` with the same HTTP status on the wire. Branch on\n  `error.name`.\n- **Amounts** — crypto amounts are decimal **strings** in whole-coin\n  units (`\"10.5\"`), never JSON numbers. Response objects also carry\n  `*_minor` fields: the integer minor-unit amount **as a string**\n  (wei-scale values overflow the JS `Number` type). Per-asset decimals\n  come from `getCurrencies` — drive amount math from there.\n- **POST parameters** — a JSON object body, a form-urlencoded body, or\n  URL query parameters (merged; body wins). `multipart/form-data` is\n  not supported. Money-moving methods are POST-only (no GET aliases).\n- **Idempotency** — money-moving methods take a caller-supplied\n  `spend_id` (required on `transfer`/`transferBatch` items, optional on\n  `createCheck`/`refundInvoice`). A retry with the same key and the\n  same parameters replays the stored result; the same key with\n  different parameters returns `409 idempotency_conflict`; a retry\n  while the original is still executing returns\n  `409 idempotency_in_progress`.\n- **Pagination** — list methods take `offset` (default 0) and `count`\n  (default 100, max 1000; `getSubscriptions`: max 500); ordering is\n  always newest first. Lists come back as `{\"items\": […]}`.\n- **Timestamps** — ISO-8601 strings with a UTC offset, or `null`.\n- **Rate limits** — per app, across all its tokens: `createInvoice`\n  60/min, `createCheck` 60/min, `refundInvoice` 30/min, `transfer`\n  30/min, `transferBatch` 10/min. Exceeding returns\n  `429 rate_limited`.\n- **Maintenance** — during platform maintenance every POST method\n  returns `503 maintenance`; GET methods keep working.\n\n## Errors common to every method\n\n| HTTP | `error.name` | when |\n|---|---|---|\n| 401 | `unauthorized` | missing/invalid/revoked token, deactivated app |\n| 403 | `scope_required` | restricted token lacking the method's scope |\n| 400 | `invalid_request` | unparsable/invalid body or parameters (POST) |\n| 429 | `rate_limited` | rate limit exceeded (rate-limited methods) |\n| 503 | `maintenance` | maintenance mode (POST methods) |\n| 500 | `internal_error` | unexpected server error |\n\nEach operation below lists only its *specific* errors.\n"
  },
  "externalDocs": {
    "description": "Crypto Pay API reference (help center)",
    "url": "https://docs.tgpaycrypto.com/crypto-pay-api-reference/"
  },
  "servers": [
    {
      "url": "https://app.tgpaycrypto.com/pay/api"
    }
  ],
  "security": [
    {
      "apiToken": []
    },
    {
      "apiTokenLegacy": []
    },
    {
      "apiTokenCompat": []
    }
  ],
  "tags": [
    {
      "name": "app",
      "description": "App identity, balance, currencies, rates, stats."
    },
    {
      "name": "invoices",
      "description": "Payment requests and refunds."
    },
    {
      "name": "checks",
      "description": "Single-use claimable crypto checks funded from the app balance."
    },
    {
      "name": "transfers",
      "description": "Payouts to Telegram users from the app balance."
    },
    {
      "name": "subscriptions",
      "description": "Extension: recurring billing — plans and subscriptions."
    }
  ],
  "paths": {
    "/getMe": {
      "get": {
        "tags": [
          "app"
        ],
        "operationId": "getMe",
        "summary": "App and token info",
        "description": "Scope: none — any valid token.\n\nReturns the app's identity, webhook configuration, and — for a\nrestricted token — the token's scopes and label (`scopes` is `null`\nfor the full-access primary token). Note: unlike Crypto Bot,\n`app_id` is a string.\n",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/AppInfo"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getBalance": {
      "get": {
        "tags": [
          "app"
        ],
        "operationId": "getBalance",
        "summary": "App balances per asset",
        "description": "Scope: `read`.\n\nOne row per platform-enabled asset (rows are present even at zero\nbalance). `onhold` is the amount locked in the app's un-activated\nchecks — refundable via `deleteCheck`, not spendable.\n",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BalanceRow"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getCurrencies": {
      "get": {
        "tags": [
          "app"
        ],
        "operationId": "getCurrencies",
        "summary": "Supported currencies",
        "description": "Scope: none — any valid token.\n\nThe authoritative list of crypto assets (`is_blockchain: true`) and\ninvoice-pricing fiats (`is_fiat: true`), with per-currency\n`decimals`. Treat this as the source of truth for what's live and\nfor amount math.\n",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Currency"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getExchangeRates": {
      "get": {
        "tags": [
          "app"
        ],
        "operationId": "getExchangeRates",
        "summary": "Crypto→fiat exchange rates",
        "description": "Scope: none — any valid token.\n\n`is_valid` is a whole-table property: `true` when the rates come\nfrom a recent successful fetch, `false` for a degraded/stale cache.\n",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExchangeRate"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getStats": {
      "get": {
        "tags": [
          "app"
        ],
        "operationId": "getStats",
        "summary": "App volume/conversion stats",
        "description": "Scope: `read`.\n\nDefaults to the last 24 hours. Naive datetimes are read as UTC; a\n`Z` suffix is accepted.\n\nSpecific errors: `400 invalid_date` (unparsable `start_at`/`end_at`).\n",
        "parameters": [
          {
            "name": "start_at",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window start (default now − 24 h)."
          },
          {
            "name": "end_at",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window end (default now)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/AppStats"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/createInvoice": {
      "post": {
        "tags": [
          "invoices"
        ],
        "operationId": "createInvoice",
        "summary": "Create an invoice",
        "description": "Scope: `invoices`. Rate limit: 60/min per app.\n\nCrypto mode (default) fixes `asset`; fiat mode fixes `fiat` and the\npayer picks one of `accepted_assets` at pay time. Invoices are paid\nfrom the payer's in-app wallet balance; a payer may also fund the\ninvoice from an external wallet (their deposit lands in their own\nwallet and the invoice auto-settles) — the merchant sees a normal\n`paid` invoice either way. The platform fee comes out of the\nmerchant's leg and is reported in `fee_amount`.\n\nFiat invoices convert at the fresh pay-time rate rounded up in the\nmerchant's favor — unless a live `rate_lock_seconds` stamp applies.\nIf no fresh rate is available, payment fails payer-side; the\nmerchant is never settled at a stale rate.\n\nSpecific errors:\n\n| HTTP | `error.name` | when |\n|---|---|---|\n| 400 | `invalid_currency` | `asset`+`fiat` together; fiat mode without `fiat`; `open_amount` on a fiat invoice |\n| 400 | `invalid_amount` | missing/unparsable/non-positive amount; `amount`+`open_amount` together; fiat amount over 2 decimal places or > 10^13 |\n| 404 | `unknown_asset` | unknown/disabled `asset`, `swap_to`, or `accepted_assets` entry |\n| 400 | `unsupported_fiat` | `fiat` not supported |\n| 400 | `paid_btn_url_required` | `paid_btn_name` set without a valid http(s) `paid_btn_url` |\n| 400 | `rate_lock_fiat_only` | `rate_lock_seconds` on a crypto-priced invoice |\n| 409 | `ratelock_disabled` | rate-lock currently unavailable platform-side |\n| 409 | `rate_unavailable` | no fresh rate for an accepted asset — retry |\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getInvoices": {
      "get": {
        "tags": [
          "invoices"
        ],
        "operationId": "getInvoices",
        "summary": "List invoices",
        "description": "Scope: `read`. Newest first.\n\n`status=active` excludes invoices already past their deadline;\n`status=expired` (a quiet extension — Crypto Bot only knows\nactive/paid) includes them.\n",
        "parameters": [
          {
            "name": "asset",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by crypto asset code."
          },
          {
            "name": "fiat",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by fiat code."
          },
          {
            "name": "invoice_ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated invoice ids."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paid",
                "expired"
              ]
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 1000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "object",
                      "required": [
                        "items"
                      ],
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Invoice"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/deleteInvoice": {
      "post": {
        "tags": [
          "invoices"
        ],
        "operationId": "deleteInvoice",
        "summary": "Delete an unpaid invoice",
        "description": "Scope: `invoices`.\n\nCancels an **unpaid** invoice (active or expired). Result: `true`.\n\nSpecific errors: `404 invoice_not_found` (unknown id or another\napp's invoice); `409 invoice_already_paid` (paid invoices can't be\ndeleted — money moved).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "invoice_id"
                ],
                "properties": {
                  "invoice_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "invoice_id"
                ],
                "properties": {
                  "invoice_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/refundInvoice": {
      "post": {
        "tags": [
          "invoices"
        ],
        "operationId": "refundInvoice",
        "summary": "Refund a paid invoice (extension)",
        "description": "**Extension over Crypto Bot.** Scope: `refunds`. Rate limit: 30/min\nper app.\n\nReturns a paid invoice's face amount — or part of it — from the app\nbalance to the payer, anonymous payers included, without revealing\nthem. The platform fee is not returned. Invoice `status` stays\n`paid`; progress is tracked in the cumulative `refunded_amount` /\n`refunded_minor`, and `refunded_at` is stamped once fully refunded.\nTriggers the opt-in `refund_completed` webhook.\n\nSpecific errors:\n\n| HTTP | `error.name` | when |\n|---|---|---|\n| 404 | `invoice_not_found` | unknown id / another app's invoice |\n| 400 | `invalid_amount` | unparsable or non-positive `amount` |\n| 409 | `invoice_not_paid` | invoice isn't `paid` |\n| 409 | `already_refunded` | nothing left to refund |\n| 409 | `amount_too_big` | `amount` exceeds the unrefunded remainder |\n| 409 | `insufficient_funds` | app balance can't cover the refund |\n| 409 | `idempotency_conflict` / `idempotency_in_progress` | see Conventions → Idempotency |\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefundInvoiceRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/RefundInvoiceRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — the updated invoice with cumulative `refunded_*` fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/createCheck": {
      "post": {
        "tags": [
          "checks"
        ],
        "operationId": "createCheck",
        "summary": "Create a check",
        "description": "Scope: `checks`. Rate limit: 60/min per app.\n\nCreates a single-use crypto check funded from the app balance;\nanyone with the link (or the pinned user) can claim it into their\nin-app wallet. The amount is locked immediately (moves from\n`available` to `onhold` in `getBalance`). Claiming fires the opt-in\n`check_activated` webhook.\n\n`spend_id` idempotency is an extension — Crypto Bot's createCheck\nmoves money with no idempotency at all.\n\nSpecific errors:\n\n| HTTP | `error.name` | when |\n|---|---|---|\n| 404 | `unknown_asset` | unknown/disabled asset |\n| 400 | `invalid_amount` | unparsable/non-positive amount |\n| 404 | `user_not_found` | `pin_to_username` matches no user |\n| 400 | `invalid_pinned_user` | non-positive pinned id |\n| 409 | `insufficient_funds` | app balance too low |\n| 409 | `idempotency_conflict` / `idempotency_in_progress` | see Conventions → Idempotency |\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/Check"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/deleteCheck": {
      "post": {
        "tags": [
          "checks"
        ],
        "operationId": "deleteCheck",
        "summary": "Cancel an active check",
        "description": "Scope: `checks`.\n\nCancels an outstanding (active) check; the locked amount refunds to\nthe app balance. Result: `true`.\n\nSpecific errors: `404 check_not_found` (unknown id or another app's\ncheck); `409 check_not_active` (already activated or deleted).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "check_id"
                ],
                "properties": {
                  "check_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "check_id"
                ],
                "properties": {
                  "check_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getChecks": {
      "get": {
        "tags": [
          "checks"
        ],
        "operationId": "getChecks",
        "summary": "List checks",
        "description": "Scope: `read`. Newest first. Deleted checks are never returned.\n",
        "parameters": [
          {
            "name": "asset",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by asset code."
          },
          {
            "name": "check_ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated check ids."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "activated"
              ]
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 1000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "object",
                      "required": [
                        "items"
                      ],
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Check"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/transfer": {
      "post": {
        "tags": [
          "transfers"
        ],
        "operationId": "transfer",
        "summary": "Pay out to a Telegram user",
        "description": "Scope: `payouts`. Rate limit: 30/min per app.\n\nPays a Telegram user out of the app balance; settles atomically at\ncreation. The recipient must already be a user of the platform — a\npayout to a mistyped id errors instead of stranding funds.\n`spend_id` is **required** (idempotency; see Conventions).\n\nAmounts are additionally subject to platform min/max USD bounds\n(best-effort at current prices).\n\nSpecific errors:\n\n| HTTP | `error.name` | when |\n|---|---|---|\n| 404 | `unknown_asset` | unknown/disabled asset |\n| 400 | `invalid_amount` | unparsable/non-positive amount |\n| 400 | `invalid_recipient` | non-positive `user_id` |\n| 404 | `user_not_found` | recipient has never used the platform |\n| 409 | `recipient_blocked` | recipient's account is blocked |\n| 400 | `amount_too_small` / `amount_too_big` | outside the USD transfer bounds |\n| 409 | `insufficient_funds` | app balance too low |\n| 409 | `idempotency_conflict` / `idempotency_in_progress` | see Conventions → Idempotency |\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TransferRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/Transfer"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/transferBatch": {
      "post": {
        "tags": [
          "transfers"
        ],
        "operationId": "transferBatch",
        "summary": "Batch payouts (extension)",
        "description": "**Extension over Crypto Bot.** Scope: `payouts`. Rate limit: 10/min\nper app.\n\nUp to 100 transfers in one call. Items settle **independently, in\norder** — a failed item never rolls back the others. Each item is\nidempotent on its `spend_id` in the same namespace as single\n`transfer`. The call itself returns HTTP 200 with `ok: true` even\nwhen items failed — check each per-item `ok`.\n\nTop-level specific error: `400 duplicate_spend_id` (two items share\na `spend_id` — nothing is executed). Per-item errors use the same\nnames as single `transfer`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/TransferRequest"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — one entry per input item, in order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "object",
                      "required": [
                        "items"
                      ],
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/TransferBatchItemResult"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getTransfers": {
      "get": {
        "tags": [
          "transfers"
        ],
        "operationId": "getTransfers",
        "summary": "List transfers",
        "description": "Scope: `read`. Newest first. `spend_id` is an exact-match filter —\nlook up a payout by your own idempotency key.\n",
        "parameters": [
          {
            "name": "asset",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by asset code."
          },
          {
            "name": "transfer_ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated transfer ids."
          },
          {
            "name": "spend_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 1000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "object",
                      "required": [
                        "items"
                      ],
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Transfer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/createSubscriptionPlan": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "operationId": "createSubscriptionPlan",
        "summary": "Create a subscription plan (extension)",
        "description": "**Extension over Crypto Bot.** Scope: `subscriptions`.\n\nThe plan is an **immutable snapshot** — the mandate a user approves\nis exactly this amount per this period; to change the price, create\na new plan and archive the old one. Users approve via the plan's\n`mini_app_subscribe_url`; the first period is charged at approval.\nRenewals bill automatically; a failed renewal puts the subscription\ninto `grace` with retries inside the platform grace window, then\n`expired`.\n\nSpecific errors: `503 subs_disabled` (feature currently unavailable\nplatform-side); `404 unknown_asset`; `400 invalid_amount`;\n`409 invalid_period` (outside the platform period bounds).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePlanRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/CreatePlanRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/Plan"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getSubscriptionPlans": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "operationId": "getSubscriptionPlans",
        "summary": "List subscription plans",
        "description": "Scope: `read`. Newest first. Each plan carries\n`active_subscribers` — the count of live (`active` + `grace`)\nsubscriptions.\n",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "object",
                      "required": [
                        "items"
                      ],
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PlanWithStats"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/archiveSubscriptionPlan": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "operationId": "archiveSubscriptionPlan",
        "summary": "Archive a plan",
        "description": "Scope: `subscriptions`.\n\nStops **new** approvals; live subscriptions keep renewing on their\nown snapshot (end them per-row via `cancelSubscription`). Archiving\nis irreversible via the API.\n\nSpecific errors: `404 plan_not_found`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan_id"
                ],
                "properties": {
                  "plan_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "plan_id"
                ],
                "properties": {
                  "plan_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — the updated plan (`archived: true`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/Plan"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/getSubscriptions": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "operationId": "getSubscriptions",
        "summary": "List subscriptions",
        "description": "Scope: `read`. Newest first. Merchants read `current_period_end` as\nthe access deadline.\n",
        "parameters": [
          {
            "name": "plan_id",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Filter by plan."
          },
          {
            "name": "user_id",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Filter by subscriber Telegram id."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "grace",
                "cancelled",
                "expired"
              ]
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "type": "object",
                      "required": [
                        "items"
                      ],
                      "properties": {
                        "items": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Subscription"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/cancelSubscription": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "operationId": "cancelSubscription",
        "summary": "Cancel a subscription",
        "description": "Scope: `subscriptions`.\n\nStops renewals (`cancelled_by: \"merchant\"`). The already-paid\nperiod stays usable until `current_period_end`; the status flips to\n`cancelled` immediately. The subscriber is notified; the opt-in\n`subscription_cancelled` webhook fires. A cancelled subscriber may\nre-approve later via the plan link.\n\nSpecific errors: `404 sub_not_found` (unknown id / another app's\nsubscription); `409 sub_not_active` (already cancelled or expired).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subscription_id"
                ],
                "properties": {
                  "subscription_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "subscription_id"
                ],
                "properties": {
                  "subscription_id": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — the updated subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "result": {
                      "$ref": "#/components/schemas/Subscription"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "webhooks": {
    "invoice_paid": {
      "post": {
        "summary": "An invoice was paid (always delivered)",
        "description": "The only webhook delivered by default (the Crypto Bot contract).\nAll other event types are opt-in per app (**Extra webhook events**\non the Merchant API screen), so a strict Crypto Bot-shaped consumer\nnever meets an unknown `update_type` unless it asked for one.\n\nEvery delivery is signed: `TgPayCrypto-API-Signature` (aliases with\nthe same value: `TgCryptoPay-API-Signature`, the name before 2026-09-07,\nand `Crypto-Pay-API-Signature`) = hex HMAC-SHA256 of the raw request\nbody, keyed by the raw 32-byte SHA-256 digest of the app's\n**primary** API token. Verify over the raw received bytes.\n\nA delivery counts as successful on any 2xx response within 10\nseconds; failures are retried with exponential backoff (first gap\n~10 s, doubling up to 8 h) for up to 17 attempts over roughly\n3 days. `update_id` is stable across retries — dedupe on it. The\nwebhook URL is never auto-disabled.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "invoice_paid"
                      },
                      "payload": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    },
    "invoice_expired": {
      "post": {
        "summary": "An invoice passed its deadline unpaid (opt-in)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "invoice_expired"
                      },
                      "payload": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    },
    "check_activated": {
      "post": {
        "summary": "A check was claimed (opt-in)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "check_activated"
                      },
                      "payload": {
                        "$ref": "#/components/schemas/Check"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    },
    "refund_completed": {
      "post": {
        "summary": "A refund was executed (opt-in)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "refund_completed"
                      },
                      "payload": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    },
    "subscription_activated": {
      "post": {
        "summary": "A user approved a plan (opt-in)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "subscription_activated"
                      },
                      "payload": {
                        "$ref": "#/components/schemas/Subscription"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    },
    "subscription_charged": {
      "post": {
        "summary": "A subscription period was billed (opt-in)",
        "description": "`charge.kind` says which period: `initial` (approval charge),\n`renewal`, or `resubscribe` (a cancelled subscriber re-approved).\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "subscription_charged"
                      },
                      "payload": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/Subscription"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "charge": {
                                "$ref": "#/components/schemas/SubscriptionCharge"
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    },
    "subscription_cancelled": {
      "post": {
        "summary": "A subscription was cancelled by either side (opt-in)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "subscription_cancelled"
                      },
                      "payload": {
                        "$ref": "#/components/schemas/Subscription"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    },
    "subscription_expired": {
      "post": {
        "summary": "The grace window ran out unpaid (opt-in)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookUpdate"
                  },
                  {
                    "properties": {
                      "update_type": {
                        "const": "subscription_expired"
                      },
                      "payload": {
                        "$ref": "#/components/schemas/Subscription"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 10 s to acknowledge."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiToken": {
        "type": "apiKey",
        "in": "header",
        "name": "TgPayCrypto-API-Token",
        "description": "The canonical token header. Shown once at app creation / rotation."
      },
      "apiTokenLegacy": {
        "type": "apiKey",
        "in": "header",
        "name": "TgCryptoPay-API-Token",
        "description": "The canonical header's name before 2026-09-07 — still accepted, so integrations built earlier keep working unchanged."
      },
      "apiTokenCompat": {
        "type": "apiKey",
        "in": "header",
        "name": "Crypto-Pay-API-Token",
        "description": "Compatibility alias for Crypto Bot client libraries. If both headers are sent, the canonical one wins."
      }
    },
    "responses": {
      "Error": {
        "description": "Error — `{\"ok\": false, \"error\": {\"code\": <HTTP status>, \"name\":\n\"<machine_name>\"}}` with the same HTTP status on the wire. See the\noperation description for its specific error names and the API\ndescription for the common ones.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "Amount": {
        "type": "string",
        "description": "Positive decimal string in whole-coin (or fiat) units, e.g. \"10.5\". Never a JSON number.",
        "examples": [
          "10.5"
        ]
      },
      "AmountMinor": {
        "type": "string",
        "description": "Extension: the integer minor-unit amount as a string (wei-scale values overflow the JS Number type).",
        "examples": [
          "10500000"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "ok",
          "error"
        ],
        "properties": {
          "ok": {
            "const": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "name"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "description": "Duplicates the HTTP status (400/401/403/404/409/429/503/500)."
              },
              "name": {
                "type": "string",
                "description": "Machine-readable error name to branch on, e.g. \"insufficient_funds\"."
              }
            }
          }
        }
      },
      "AppInfo": {
        "type": "object",
        "required": [
          "app_id",
          "name",
          "payment_processing_bot_username"
        ],
        "properties": {
          "app_id": {
            "type": "string",
            "description": "The app's public id (a string — unlike Crypto Bot's numeric id)."
          },
          "name": {
            "type": "string"
          },
          "payment_processing_bot_username": {
            "type": "string",
            "description": "The Telegram bot whose Mini App processes payments."
          },
          "webhook_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "webhook_events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Extension: extended webhook update types the app opted into (invoice_paid is always delivered and not listed here)."
          },
          "scopes": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "enum": [
                "read",
                "invoices",
                "refunds",
                "payouts",
                "checks",
                "subscriptions"
              ]
            },
            "description": "Extension: this token's scopes, sorted; null = full-access primary token."
          },
          "token_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extension: the restricted token's label; null for the primary token."
          }
        }
      },
      "BalanceRow": {
        "type": "object",
        "required": [
          "currency_code",
          "available",
          "onhold"
        ],
        "properties": {
          "currency_code": {
            "type": "string",
            "examples": [
              "USDT"
            ]
          },
          "available": {
            "$ref": "#/components/schemas/Amount"
          },
          "onhold": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Amount"
              }
            ],
            "description": "Funds locked in the app's un-activated checks — refundable via deleteCheck, not spendable."
          },
          "amount_minor": {
            "$ref": "#/components/schemas/AmountMinor"
          }
        }
      },
      "Currency": {
        "type": "object",
        "required": [
          "code",
          "name",
          "decimals",
          "is_blockchain",
          "is_stablecoin",
          "is_fiat"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "examples": [
              "Tether USD"
            ]
          },
          "decimals": {
            "type": "integer",
            "description": "Minor-unit exponent (crypto: per asset, up to 18; fiat: always 2)."
          },
          "is_blockchain": {
            "type": "boolean"
          },
          "is_stablecoin": {
            "type": "boolean"
          },
          "is_fiat": {
            "type": "boolean"
          }
        }
      },
      "ExchangeRate": {
        "type": "object",
        "required": [
          "is_valid",
          "is_crypto",
          "is_fiat",
          "source",
          "target",
          "rate"
        ],
        "properties": {
          "is_valid": {
            "type": "boolean",
            "description": "Whole-table property — false for a degraded/stale rate cache."
          },
          "is_crypto": {
            "type": "boolean"
          },
          "is_fiat": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "description": "Crypto asset code."
          },
          "target": {
            "type": "string",
            "description": "Fiat code."
          },
          "rate": {
            "type": "string",
            "description": "Fiat per 1 whole unit of source",
            "fixed-point string.": null
          }
        }
      },
      "AppStats": {
        "type": "object",
        "properties": {
          "volume": {
            "type": "number",
            "description": "USD value of invoices paid in the window, at current prices (best-effort)."
          },
          "conversion": {
            "type": "number",
            "description": "Paid/created percentage over the window, 2 decimal places."
          },
          "unique_users_count": {
            "type": "integer"
          },
          "created_invoice_count": {
            "type": "integer"
          },
          "paid_invoice_count": {
            "type": "integer"
          },
          "start_at": {
            "type": "string",
            "format": "date-time"
          },
          "end_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateInvoiceRequest": {
        "type": "object",
        "properties": {
          "currency_type": {
            "type": "string",
            "enum": [
              "crypto",
              "fiat"
            ],
            "default": "crypto"
          },
          "asset": {
            "type": "string",
            "description": "Crypto mode: the invoice asset, e.g. USDT. Forbidden together with fiat."
          },
          "fiat": {
            "type": "string",
            "description": "Fiat mode: the pricing fiat (is_fiat rows of getCurrencies)."
          },
          "accepted_assets": {
            "description": "Fiat mode only: assets the payer may pay with — comma-separated string (Crypto Bot form) or JSON array. Omitted = all supported assets.",
            "oneOf": [
              {
                "type": "string",
                "examples": [
                  "USDT,GRAM"
                ]
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Amount"
              }
            ],
            "description": "Required unless open_amount. Crypto mode: in asset units; fiat mode: in fiat units, max 2 decimal places."
          },
          "open_amount": {
            "type": "boolean",
            "default": false,
            "description": "Extension, crypto mode only: no fixed amount — the payer enters it at pay time. Mutually exclusive with amount."
          },
          "description": {
            "type": "string",
            "maxLength": 1024,
            "description": "Shown to the payer."
          },
          "hidden_message": {
            "type": "string",
            "maxLength": 2048,
            "description": "Shown to the payer only after payment."
          },
          "payload": {
            "type": "string",
            "maxLength": 4096,
            "description": "Opaque merchant data echoed back in the invoice object and webhook."
          },
          "allow_comments": {
            "type": "boolean",
            "default": true
          },
          "allow_anonymous": {
            "type": "boolean",
            "default": true
          },
          "paid_btn_name": {
            "type": "string",
            "enum": [
              "viewItem",
              "openChannel",
              "openBot",
              "callback"
            ],
            "description": "Post-payment CTA button."
          },
          "paid_btn_url": {
            "type": "string",
            "maxLength": 512,
            "description": "Required when paid_btn_name is set; must start with http:// or https://."
          },
          "swap_to": {
            "type": "string",
            "description": "Auto-swap the received amount to this asset after payment, best-effort — payment never fails because of the swap."
          },
          "expires_in": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2678400,
            "description": "Seconds until expiry (max 31 days); 0 or omitted = never expires."
          },
          "rate_lock_seconds": {
            "type": "integer",
            "minimum": 0,
            "maximum": 86400,
            "description": "Extension, fiat mode only: stamp the current rate for every accepted asset and honor it for the window. The server clamps the window to [60 s, the platform maximum]."
          }
        }
      },
      "Invoice": {
        "type": "object",
        "description": "The invoice object — API results and invoice webhook payloads.",
        "properties": {
          "invoice_id": {
            "type": "integer"
          },
          "hash": {
            "type": "string",
            "description": "Public id (also inside pay_url)."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paid",
              "expired"
            ]
          },
          "currency_type": {
            "type": "string",
            "enum": [
              "crypto",
              "fiat"
            ]
          },
          "asset": {
            "type": [
              "string",
              "null"
            ],
            "description": "Crypto invoice: the fixed asset. Fiat invoice: always null here (see paid_asset)."
          },
          "fiat": {
            "type": [
              "string",
              "null"
            ]
          },
          "accepted_assets": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Fiat invoices: the concrete list the payer may pay with; null for crypto invoices."
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Face amount: fiat units for fiat invoices, crypto units otherwise. Null for an unpaid open-amount invoice."
          },
          "amount_minor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extension: crypto amount in minor units (string); null while a fiat/open-amount invoice is unpaid."
          },
          "paid_asset": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fiat invoices, once paid: the asset the payer chose."
          },
          "paid_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fiat invoices, once paid: the crypto amount actually paid."
          },
          "paid_fiat_rate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fiat invoices, once paid: the fiat-per-asset rate used."
          },
          "paid_usd_rate": {
            "type": [
              "string",
              "null"
            ],
            "description": "USD price of the paid asset stamped at pay time."
          },
          "fee_asset": {
            "type": [
              "string",
              "null"
            ]
          },
          "fee_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Platform fee, charged to the merchant's leg — the authoritative number for reconciliation."
          },
          "fee": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated alias of fee_amount (Crypto Bot legacy)."
          },
          "usd_rate": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated alias of paid_usd_rate."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "hidden_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "payload": {
            "type": [
              "string",
              "null"
            ]
          },
          "allow_comments": {
            "type": "boolean"
          },
          "allow_anonymous": {
            "type": "boolean"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "The payer's comment (only if allowed)."
          },
          "paid_anonymously": {
            "type": "boolean"
          },
          "is_anonymous": {
            "type": "boolean",
            "description": "Alias of paid_anonymously."
          },
          "paid_btn_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_btn_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "swap_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_swapped": {
            "type": "boolean"
          },
          "swapped_uid": {
            "type": [
              "string",
              "null"
            ]
          },
          "swapped_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "swapped_rate": {
            "type": [
              "string",
              "null"
            ]
          },
          "swapped_output": {
            "type": [
              "string",
              "null"
            ]
          },
          "swapped_usd_rate": {
            "type": [
              "string",
              "null"
            ]
          },
          "swapped_usd_amount": {
            "type": [
              "string",
              "null"
            ]
          },
          "pay_url": {
            "type": "string",
            "description": "t.me deep link the payer opens."
          },
          "bot_invoice_url": {
            "type": "string",
            "description": "Alias of pay_url."
          },
          "mini_app_invoice_url": {
            "type": "string",
            "description": "Alias of pay_url."
          },
          "web_app_invoice_url": {
            "type": "string",
            "description": "Alias of pay_url."
          },
          "paid_by_user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The payer's Telegram id — null when the payer paid anonymously."
          },
          "open_amount": {
            "type": "boolean",
            "description": "Extension: this is an open-amount (donation) invoice."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "refunded_amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extension: cumulative refunded amount."
          },
          "refunded_minor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extension: the same in minor units (string)."
          },
          "refunded_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Extension: stamped once fully refunded (status stays paid)."
          },
          "rate_lock_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Extension: rate-lock deadline; null when no lock was requested."
          },
          "rate_lock_rates": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            },
            "description": "Extension: {asset_code: rate_string} stamped at creation — the exact rates quoted while the lock is live."
          },
          "expiration_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Alias of expiration_date."
          }
        }
      },
      "RefundInvoiceRequest": {
        "type": "object",
        "required": [
          "invoice_id"
        ],
        "properties": {
          "invoice_id": {
            "type": "integer",
            "minimum": 1
          },
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Amount"
              }
            ],
            "description": "Refund amount in the invoice's asset. Omitted = the full unrefunded remainder. Partial refunds accumulate up to the face amount."
          },
          "spend_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Optional idempotency key — strongly recommended; a timed-out retry replays instead of refunding twice."
          }
        }
      },
      "CreateCheckRequest": {
        "type": "object",
        "required": [
          "asset",
          "amount"
        ],
        "properties": {
          "asset": {
            "type": "string"
          },
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "pin_to_user_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Restrict claiming to this Telegram user id."
          },
          "pin_to_username": {
            "type": "string",
            "maxLength": 64,
            "description": "Restrict by @username (leading @ optional, case-insensitive); ignored when pin_to_user_id is also given. Must belong to an existing user of the platform."
          },
          "spend_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Extension: optional idempotency key."
          }
        }
      },
      "Check": {
        "type": "object",
        "properties": {
          "check_id": {
            "type": "integer"
          },
          "hash": {
            "type": "string",
            "description": "Public id."
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "amount_minor": {
            "$ref": "#/components/schemas/AmountMinor"
          },
          "bot_check_url": {
            "type": "string",
            "description": "t.me claim deep link."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "activated"
            ]
          },
          "pin_to_user_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "TransferRequest": {
        "type": "object",
        "required": [
          "user_id",
          "asset",
          "amount",
          "spend_id"
        ],
        "properties": {
          "user_id": {
            "type": "integer",
            "minimum": 1,
            "description": "Recipient's Telegram user id."
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Amount"
              }
            ],
            "description": "Also subject to platform min/max USD bounds."
          },
          "spend_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Required idempotency key, unique per app."
          },
          "comment": {
            "type": "string",
            "maxLength": 1024,
            "description": "Shown to the recipient."
          },
          "disable_send_notification": {
            "type": "boolean",
            "default": false,
            "description": "true = don't send the recipient a Telegram notification."
          }
        }
      },
      "Transfer": {
        "type": "object",
        "properties": {
          "transfer_id": {
            "type": "integer"
          },
          "hash": {
            "type": "string"
          },
          "user_id": {
            "type": "integer"
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "amount_minor": {
            "$ref": "#/components/schemas/AmountMinor"
          },
          "spend_id": {
            "type": "string"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ],
            "description": "Transfers settle atomically."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TransferBatchItemResult": {
        "type": "object",
        "required": [
          "ok",
          "spend_id"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "spend_id": {
            "type": "string"
          },
          "result": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Transfer"
              }
            ],
            "description": "Present when ok is true."
          },
          "error": {
            "type": "object",
            "description": "Present when ok is false — same error names as single transfer.",
            "properties": {
              "code": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              }
            }
          }
        }
      },
      "CreatePlanRequest": {
        "type": "object",
        "required": [
          "name",
          "asset",
          "amount",
          "period_days"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Amount"
              }
            ],
            "description": "The per-period charge."
          },
          "period_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "Additionally bounded server-side to the platform minimum — outside returns 409 invalid_period."
          }
        }
      },
      "Plan": {
        "type": "object",
        "properties": {
          "plan_id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "amount_minor": {
            "$ref": "#/components/schemas/AmountMinor"
          },
          "period_days": {
            "type": "integer"
          },
          "archived": {
            "type": "boolean",
            "description": "Archived plans accept no new approvals."
          },
          "mini_app_subscribe_url": {
            "type": "string",
            "description": "t.me deep link users open to approve."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PlanWithStats": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Plan"
          },
          {
            "type": "object",
            "properties": {
              "active_subscribers": {
                "type": "integer",
                "description": "Count of live (active + grace) subscriptions on the plan."
              }
            }
          }
        ]
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "integer"
          },
          "plan_id": {
            "type": "integer"
          },
          "user_id": {
            "type": "integer",
            "description": "Subscriber's Telegram id."
          },
          "asset": {
            "type": "string",
            "description": "Plan snapshot."
          },
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Amount"
              }
            ],
            "description": "Snapshot per-period amount."
          },
          "amount_minor": {
            "$ref": "#/components/schemas/AmountMinor"
          },
          "period_days": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "grace",
              "cancelled",
              "expired"
            ],
            "description": "grace = renewal failed, retrying inside the grace window."
          },
          "auto_renew": {
            "type": "boolean"
          },
          "period_no": {
            "type": "integer",
            "description": "Number of paid periods so far."
          },
          "current_period_start": {
            "type": "string",
            "format": "date-time"
          },
          "current_period_end": {
            "type": "string",
            "format": "date-time",
            "description": "The paid-through deadline — read this as the access deadline."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelled_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "user",
              "merchant",
              null
            ]
          },
          "expired_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "SubscriptionCharge": {
        "type": "object",
        "description": "The charge sub-object attached to subscription_charged webhook payloads.",
        "properties": {
          "period_no": {
            "type": "integer"
          },
          "kind": {
            "type": "string",
            "enum": [
              "initial",
              "renewal",
              "resubscribe"
            ]
          },
          "asset": {
            "type": "string"
          },
          "amount": {
            "$ref": "#/components/schemas/Amount"
          },
          "fee": {
            "$ref": "#/components/schemas/Amount"
          },
          "paid_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookUpdate": {
        "type": "object",
        "required": [
          "update_id",
          "update_type",
          "request_date",
          "payload"
        ],
        "properties": {
          "update_id": {
            "type": "integer",
            "description": "Stable across redeliveries of the same event — dedupe on it."
          },
          "update_type": {
            "type": "string",
            "enum": [
              "invoice_paid",
              "invoice_expired",
              "check_activated",
              "refund_completed",
              "subscription_activated",
              "subscription_charged",
              "subscription_cancelled",
              "subscription_expired"
            ]
          },
          "request_date": {
            "type": "string",
            "format": "date-time",
            "description": "Stamped per delivery attempt."
          },
          "payload": {
            "description": "The full object for the event type."
          }
        }
      }
    }
  }
}