
openapi: '3.1.0'
info:
  title: 'MDPay API'
  version: '1.0.0'
  summary: 'Self-hosted TON payment service provider — anonymous by design.'
  description: '# MDPay API

**MDPay** is a self-hosted PSP (payment service provider) for the TON network.
You generate an *account key* in one request; everything else (invoices,
webhooks, balances, withdrawals) is keyed off that identity. No emails, no KYC
on the platform itself.

## Quick start

```bash
# 1. Create an account
curl -X POST https://tonpay.y7v.lol/v1/accounts \
  -H ''content-type: application/json'' \
  -d ''{}''

# Response → { "account_key": "TON-...", "user_id": "...", ... }

# 2. Issue an invoice
curl -X POST https://tonpay.y7v.lol/v1/invoices \
  -H ''X-Account-Key: TON-A8K4M6N9P3-QXW2Y7Z5R8'' \
  -H ''content-type: application/json'' \
  -d ''{ "amount_ton": "2.5", "description": "Order #42" }''
```

## Authentication

Three credential types are accepted:

| Scheme           | Header                       | Use case                                 |
|------------------|------------------------------|------------------------------------------|
| Account key      | `X-Account-Key: TON-...`     | Owner-level access to your own account.  |
| API key (Bearer) | `Authorization: Bearer tp_live_...` | Programmatic use, scoped, revocable. |
| Admin bootstrap  | `Authorization: Bearer admin <token>` | One-time promotion of the first admin. |

## Money

All amounts in nano-TON are returned as **strings** (BigInt-safe). Human-readable
amounts (`amount_ton`) follow `/^\d+(\.\d{1,9})?$/`.

## Errors

Every non-2xx response has the shape:

```json
{ "error": { "code": "snake_case_code", "message": "human readable", "details": null } }
```

## Versioning

The base path is `/v1`. Breaking changes ship under `/v2`. Additive changes —
new fields, new endpoints — happen in place.'
  contact:
    name: 'MDPay support'
    url: 'https://t.me/n0s3nse'
    email: 'admin@y7v.lol'
  license:
    name: 'Proprietary — All rights reserved'
    url: 'https://tonpay.y7v.lol/legal/terms'
  x-logo:
    url: 'https://tonpay.y7v.lol/icon.svg'
    altText: 'MDPay'
servers:
- url: 'https://tonpay.y7v.lol'
  description: 'Production'
- url: 'http://localhost:4100'
  description: 'Local development'
tags:
- name: 'Accounts'
  description: 'Account creation, profile, sessions, recovery.'
- name: 'Invoices'
  description: 'Create, list, cancel and resolve invoices.'
- name: 'Webhooks'
  description: 'Event subscriptions, signing secrets, delivery log.'
- name: 'ApiKeys'
  description: 'Issue and revoke `tp_live_…` API keys.'
- name: 'Withdrawals'
  description: 'Move TON from the platform balance to your wallet.'
- name: 'Balance'
  description: 'Live and historical balance views.'
- name: 'Admin'
  description: 'Operator endpoints — admin only.'
- name: 'Health'
  description: 'Liveness and readiness probes.'
security:
- accountKey: []
- bearer: []
components:
  securitySchemes:
    accountKey:
      type: 'apiKey'
      in: 'header'
      name: 'X-Account-Key'
      description: 'Owner-level credential. Format: `TON-XXXX…` (20–64 chars).'
    bearer:
      type: 'http'
      scheme: 'bearer'
      bearerFormat: 'tp_live_…'
      description: 'API key issued via `POST /v1/api-keys`. Send as `Authorization: Bearer tp_live_…`.'
    adminBootstrap:
      type: 'http'
      scheme: 'bearer'
      bearerFormat: 'admin <ADMIN_BOOTSTRAP_TOKEN>'
      description: 'One-time admin promotion. Send `Authorization: Bearer admin <ADMIN_BOOTSTRAP_TOKEN>` when creating the very first account; the resulting user gets `is_admin=true`. Refused if any admin already exists.'
  schemas:
    Error:
      type: 'object'
      required:
      - 'error'
      properties:
        error:
          type: 'object'
          required:
          - 'code'
          - 'message'
          properties:
            code:
              type: 'string'
              example: 'unauthorized'
            message:
              type: 'string'
              example: 'authentication required'
            details: {}
    Account:
      type: 'object'
      required:
      - 'account_key'
      - 'user_id'
      - 'created_at'
      - 'is_admin'
      properties:
        account_key:
          type: 'string'
          description: 'The single owner-level credential. Store securely — it is shown only once.'
          example: 'TON-A8K4M6N9P3-QXW2Y7Z5R8'
        user_id:
          type: 'string'
          format: 'uuid'
          example: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        created_at:
          type: 'string'
          format: 'date-time'
        is_admin:
          type: 'boolean'
          example: false
    User:
      type: 'object'
      required:
      - 'user_id'
      - 'is_admin'
      - 'disabled'
      - 'created_at'
      properties:
        user_id:
          type: 'string'
          format: 'uuid'
          example: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        telegram_username:
          type:
          - 'string'
          - 'null'
          example: 'satoshi'
        default_webhook_url:
          type:
          - 'string'
          - 'null'
          format: 'uri'
        is_admin:
          type: 'boolean'
        disabled:
          type: 'boolean'
        created_at:
          type: 'string'
          format: 'date-time'
    RecoveryPhrase:
      type: 'object'
      required:
      - 'account_key'
      - 'user_id'
      description: 'Returned by `POST /v1/recover`. The platform never stores plaintext keys — recovery requires the original 24-word mnemonic of your wallet.'
      properties:
        account_key:
          type: 'string'
          example: 'TON-A8K4M6N9P3-QXW2Y7Z5R8'
        user_id:
          type: 'string'
          format: 'uuid'
          example: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
    Invoice:
      type: 'object'
      required:
      - 'id'
      - 'status'
      - 'amount_ton'
      - 'amount_nanoton'
      - 'asset'
      - 'address'
      - 'expires_at'
      - 'created_at'
      properties:
        id:
          type: 'string'
          format: 'uuid'
          example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        public_id:
          type: 'string'
          pattern: '^[0-9A-Z]{10}$'
          example: 'V7HQ3M2X9P'
        status:
          type: 'string'
          enum:
          - 'pending'
          - 'paid'
          - 'expired'
          - 'cancelled'
        amount_ton:
          type: 'string'
          pattern: '^\d+(\.\d{1,9})?$'
          description: 'Human-readable TON amount.'
          example: '2.5'
        amount_nanoton:
          type: 'string'
          description: 'Always stringified — nano-TON values are BigInt.'
          example: '2500000000'
        asset:
          type: 'string'
          example: 'TON'
        description:
          type: 'string'
          example: 'Order #42'
        payload_text:
          type: 'string'
          description: 'Comment the payer must include in the TON transfer.'
          example: 'tp-V7HQ3M2X9P'
        address:
          type: 'string'
          example: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
        ton_link:
          type: 'string'
          format: 'uri'
        tonkeeper_link:
          type: 'string'
          format: 'uri'
        public_url:
          type: 'string'
          format: 'uri'
        expires_at:
          type: 'string'
          format: 'date-time'
        paid_at:
          type:
          - 'string'
          - 'null'
          format: 'date-time'
        paid_tx_hash:
          type:
          - 'string'
          - 'null'
          example: null
        paid_amount_nanoton:
          type:
          - 'string'
          - 'null'
        paid_from_address:
          type:
          - 'string'
          - 'null'
        metadata:
          type: 'object'
          additionalProperties: true
          example:
            order_id: '42'
        callback_url:
          type:
          - 'string'
          - 'null'
          format: 'uri'
        created_at:
          type: 'string'
          format: 'date-time'
    PublicInvoice:
      type: 'object'
      description: 'Unauthenticated public view of an invoice — safe to embed in a checkout page.'
      required:
      - 'id'
      - 'status'
      - 'amount_ton'
      - 'amount_nanoton'
      - 'asset'
      - 'address'
      - 'expires_at'
      properties:
        id:
          type: 'string'
          format: 'uuid'
        status:
          type: 'string'
          enum:
          - 'pending'
          - 'paid'
          - 'expired'
          - 'cancelled'
        amount_ton:
          type: 'string'
        amount_nanoton:
          type: 'string'
        asset:
          type: 'string'
          example: 'TON'
        description:
          type: 'string'
        address:
          type: 'string'
          example: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
        payload_text:
          type: 'string'
        ton_link:
          type: 'string'
          format: 'uri'
        tonkeeper_link:
          type: 'string'
          format: 'uri'
        expires_at:
          type: 'string'
          format: 'date-time'
        paid_at:
          type:
          - 'string'
          - 'null'
          format: 'date-time'
        paid_tx_hash:
          type:
          - 'string'
          - 'null'
    Webhook:
      type: 'object'
      required:
      - 'id'
      - 'url'
      - 'events'
      - 'disabled'
      - 'created_at'
      properties:
        id:
          type: 'string'
          format: 'uuid'
          example: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        url:
          type: 'string'
          format: 'uri'
          example: 'https://example.com/tonpay/webhook'
        events:
          type: 'array'
          items:
            type: 'string'
          example:
          - 'invoice.paid'
          - 'invoice.expired'
        signing_secret:
          type:
          - 'string'
          - 'null'
          description: 'Returned **only once** when created or rotated.'
          example: 'whsec_a1b2c3d4e5f6...'
        disabled:
          type: 'boolean'
        created_at:
          type: 'string'
          format: 'date-time'
    ApiKey:
      type: 'object'
      required:
      - 'id'
      - 'prefix'
      - 'name'
      - 'created_at'
      properties:
        id:
          type: 'string'
          format: 'uuid'
          example: 'd4a4ec03-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        prefix:
          type: 'string'
          example: 'tp_live_94b7e8f0'
        key:
          type: 'string'
          description: 'Full secret — returned ONLY when the key is created or rotated. Store it now.'
          example: 'tp_live_94b7e8f0a1d24c9b8e6f3d2a7c1b9e0d'
        name:
          type: 'string'
          example: 'production-server-1'
        last_used_at:
          type:
          - 'string'
          - 'null'
          format: 'date-time'
        revoked_at:
          type:
          - 'string'
          - 'null'
          format: 'date-time'
        created_at:
          type: 'string'
          format: 'date-time'
    Withdrawal:
      type: 'object'
      required:
      - 'id'
      - 'status'
      - 'to_address'
      - 'amount_nanoton'
      - 'requested_at'
      properties:
        id:
          type: 'string'
          format: 'uuid'
          example: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        status:
          type: 'string'
          enum:
          - 'pending'
          - 'queued'
          - 'sent'
          - 'confirmed'
          - 'failed'
        to_address:
          type: 'string'
          example: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
        amount_nanoton:
          type: 'string'
          example: '1500000000'
        fee_nanoton:
          type:
          - 'string'
          - 'null'
          example: '5000000'
        net_nanoton:
          type:
          - 'string'
          - 'null'
          example: '1495000000'
        comment:
          type:
          - 'string'
          - 'null'
          example: 'payout'
        tx_hash:
          type:
          - 'string'
          - 'null'
        failure_reason:
          type:
          - 'string'
          - 'null'
        requested_at:
          type: 'string'
          format: 'date-time'
        sent_at:
          type:
          - 'string'
          - 'null'
          format: 'date-time'
        confirmed_at:
          type:
          - 'string'
          - 'null'
          format: 'date-time'
    Balance:
      type: 'object'
      required:
      - 'available_nanoton'
      - 'pending_nanoton'
      - 'total_received_nanoton'
      properties:
        available_nanoton:
          type: 'string'
          description: 'Funds you can withdraw right now. Always stringified.'
          example: '12500000000'
        pending_nanoton:
          type: 'string'
          description: 'Funds locked by in-flight withdrawals.'
          example: '0'
        total_received_nanoton:
          type: 'string'
          description: 'Lifetime gross volume.'
          example: '57500000000'
        commission_bps:
          type: 'integer'
          description: 'Platform commission applied to incoming payments, in basis points.'
          example: 100
    Wallet:
      type: 'object'
      required:
      - 'id'
      - 'address'
      - 'version'
      - 'workchain'
      - 'created_at'
      properties:
        id:
          type: 'string'
          format: 'uuid'
          example: 'e4a4ec04-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        address:
          type: 'string'
          example: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
        label:
          type: 'string'
          example: 'default'
        version:
          type: 'string'
          enum:
          - 'v4r2'
          - 'v5r1'
          example: 'v4r2'
        workchain:
          type: 'integer'
          minimum: -1
          maximum: 1
          example: 0
        disabled:
          type: 'boolean'
          example: false
        created_at:
          type: 'string'
          format: 'date-time'
        updated_at:
          type: 'string'
          format: 'date-time'
paths:
  '/health':
    get:
      tags:
      - 'Health'
      operationId: 'getHealth'
      summary: 'Liveness probe'
      description: 'Returns `200 { status: "ok" }` whenever the process is up.'
      security: []
      responses:
        '200':
          description: 'Process is alive.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                - 'status'
                properties:
                  status:
                    type: 'string'
                    enum:
                    - 'ok'
              example:
                status: 'ok'
  '/ready':
    get:
      tags:
      - 'Health'
      operationId: 'getReady'
      summary: 'Readiness probe'
      description: 'Returns `200` only when DB and Redis are both reachable. Useful for k8s/nginx upstream health checks.'
      security: []
      responses:
        '200':
          description: 'All dependencies healthy.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  status:
                    type: 'string'
                    enum:
                    - 'ok'
                  db:
                    type: 'string'
                    enum:
                    - 'ok'
                  redis:
                    type: 'string'
                    enum:
                    - 'ok'
              example:
                status: 'ok'
                db: 'ok'
                redis: 'ok'
        '503':
          description: 'A dependency is down.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  status:
                    type: 'string'
                    enum:
                    - 'down'
                  db:
                    type: 'string'
                    enum:
                    - 'ok'
                    - 'down'
                  redis:
                    type: 'string'
                    enum:
                    - 'ok'
                    - 'down'
              example:
                status: 'down'
                db: 'ok'
                redis: 'down'
  '/v1/accounts':
    post:
      tags:
      - 'Accounts'
      operationId: 'createAccount'
      summary: 'Create a new account'
      description: 'Mints a fresh account key. Rate-limited to 3 requests / minute / IP. If `Authorization: Bearer admin <ADMIN_BOOTSTRAP_TOKEN>` is supplied AND no admin user exists yet, the new account becomes the platform admin.'
      security:
      - {}
      - adminBootstrap: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: 'object'
              properties:
                telegram_username:
                  type: 'string'
                  pattern: '^[A-Za-z0-9_]+$'
                  minLength: 1
                  maxLength: 64
                  example: 'satoshi'
                default_webhook_url:
                  type: 'string'
                  format: 'uri'
                  maxLength: 2048
                  example: 'https://example.com/tonpay/webhook'
            examples:
              minimal:
                value: {}
              withTelegram:
                value:
                  telegram_username: 'satoshi'
      responses:
        '200':
          description: 'Account created.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Account'
              example:
                account_key: 'TON-A8K4M6N9P3-QXW2Y7Z5R8'
                user_id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                created_at: '2026-05-24T12:00:00.000Z'
                is_admin: false
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
        '429':
          description: 'Too many requests.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'rate_limited'
                  message: 'Too many requests.'
  '/v1/me':
    get:
      tags:
      - 'Accounts'
      operationId: 'getMe'
      summary: 'Get current user profile'
      description: 'Returns the public profile of the authenticated user.'
      responses:
        '200':
          description: 'Current user profile.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/User'
              example:
                user_id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                telegram_username: 'satoshi'
                default_webhook_url: null
                is_admin: false
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
    patch:
      tags:
      - 'Accounts'
      operationId: 'updateMe'
      summary: 'Update current user profile'
      description: 'Patch the telegram handle or default webhook URL on your own account.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              properties:
                telegram_username:
                  type:
                  - 'string'
                  - 'null'
                  pattern: '^[A-Za-z0-9_]+$'
                  maxLength: 64
                default_webhook_url:
                  type:
                  - 'string'
                  - 'null'
                  format: 'uri'
                  maxLength: 2048
            example:
              telegram_username: 'new_handle'
      responses:
        '200':
          description: 'Updated user profile.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/User'
              example:
                user_id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                telegram_username: 'new_handle'
                default_webhook_url: null
                is_admin: false
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
  '/v1/auth/session':
    post:
      tags:
      - 'Accounts'
      operationId: 'createSession'
      summary: 'Exchange an account key for a browser session cookie'
      description: 'Used by the dashboard. Send your account key in `X-Account-Key`, receive a `tp_sess` HttpOnly cookie and a CSRF token cookie + body field for double-submit.'
      responses:
        '200':
          description: 'Session created — cookies set in response.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                - 'ok'
                - 'csrf_token'
                properties:
                  ok:
                    type: 'boolean'
                  csrf_token:
                    type: 'string'
                    minLength: 32
                    maxLength: 32
              example:
                ok: true
                csrf_token: '94b7e8f0a1d24c9b8e6f3d2a7c1b9e0d'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
  '/v1/auth/logout':
    post:
      tags:
      - 'Accounts'
      operationId: 'logout'
      summary: 'Destroy the current browser session'
      description: 'Clears the `tp_sess` and `tp_csrf` cookies.'
      security: []
      responses:
        '200':
          description: 'Session cleared.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  ok:
                    type: 'boolean'
              example:
                ok: true
  '/v1/recover':
    post:
      tags:
      - 'Accounts'
      operationId: 'recoverAccount'
      summary: 'Recover account key via wallet mnemonic'
      description: 'For self-hosted deployments where the operator can prove ownership of the receive wallet. Provide the 24-word mnemonic; if it matches the configured receive wallet, the platform returns a fresh account-key bound to the operator user.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'mnemonic'
              properties:
                mnemonic:
                  type: 'string'
                  description: '24-word BIP-39 phrase, space-separated.'
                  minLength: 1
                  maxLength: 2048
            example:
              mnemonic: 'word1 word2 word3 ... word24'
      responses:
        '200':
          description: 'Recovery successful — new account key issued.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/RecoveryPhrase'
              example:
                account_key: 'TON-A8K4M6N9P3-QXW2Y7Z5R8'
                user_id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
        '429':
          description: 'Too many requests.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'rate_limited'
                  message: 'Too many requests.'
  '/v1/invoices':
    post:
      tags:
      - 'Invoices'
      operationId: 'createInvoice'
      summary: 'Create an invoice'
      description: 'Generates a pending invoice with a unique TON payload (comment). Pass `Idempotency-Key` header to make this call safe to retry — the same key returns the original invoice for up to 24 h.'
      parameters:
      - name: 'Idempotency-Key'
        in: 'header'
        required: false
        description: 'Client-supplied retry key. 8–64 chars.'
        schema:
          type: 'string'
          minLength: 8
          maxLength: 64
        example: 'order-42-v1'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'amount_ton'
              properties:
                amount_ton:
                  type: 'string'
                  pattern: '^\d+(\.\d{1,9})?$'
                  example: '2.5'
                description:
                  type: 'string'
                  maxLength: 512
                  example: 'Order #42'
                expires_in_sec:
                  type: 'integer'
                  minimum: 60
                  maximum: 604800
                  default: 3600
                metadata:
                  type: 'object'
                  additionalProperties: true
                  example:
                    order_id: '42'
                callback_url:
                  type: 'string'
                  format: 'uri'
                  maxLength: 2048
            example:
              amount_ton: '2.5'
              description: 'Order #42'
              metadata:
                order_id: '42'
      responses:
        '200':
          description: 'Invoice created.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Invoice'
              example:
                id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                public_id: 'V7HQ3M2X9P'
                status: 'pending'
                amount_ton: '2.5'
                amount_nanoton: '2500000000'
                asset: 'TON'
                description: 'Order #42'
                payload_text: 'tp-V7HQ3M2X9P'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                ton_link: 'ton://transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                tonkeeper_link: 'https://app.tonkeeper.com/transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                public_url: 'https://tonpay.y7v.lol/p/7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                expires_at: '2026-05-24T13:05:00.000Z'
                paid_at: null
                paid_tx_hash: null
                paid_amount_nanoton: null
                paid_from_address: null
                metadata:
                  order_id: '42'
                callback_url: null
                created_at: '2026-05-24T12:05:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
        '503':
          description: 'No receive wallet has been configured yet.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'wallet_not_configured'
                  message: 'No receive wallet has been configured yet.'
    get:
      tags:
      - 'Invoices'
      operationId: 'listInvoices'
      summary: 'List invoices'
      description: 'Cursor-paginated. Most recent first.'
      parameters:
      - name: 'status'
        in: 'query'
        schema:
          type: 'string'
          enum:
          - 'pending'
          - 'paid'
          - 'expired'
          - 'cancelled'
      - name: 'cursor'
        in: 'query'
        schema:
          type: 'string'
          maxLength: 64
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 20
      responses:
        '200':
          description: 'Paginated invoices.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                - 'data'
                properties:
                  data:
                    type: 'array'
                    items:
                      '$ref': '#/components/schemas/Invoice'
                  next_cursor:
                    type:
                    - 'string'
                    - 'null'
              example:
                data:
                - id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  public_id: 'V7HQ3M2X9P'
                  status: 'pending'
                  amount_ton: '2.5'
                  amount_nanoton: '2500000000'
                  asset: 'TON'
                  description: 'Order #42'
                  payload_text: 'tp-V7HQ3M2X9P'
                  address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                  ton_link: 'ton://transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                  tonkeeper_link: 'https://app.tonkeeper.com/transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                  public_url: 'https://tonpay.y7v.lol/p/7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  expires_at: '2026-05-24T13:05:00.000Z'
                  paid_at: null
                  paid_tx_hash: null
                  paid_amount_nanoton: null
                  paid_from_address: null
                  metadata:
                    order_id: '42'
                  callback_url: null
                  created_at: '2026-05-24T12:05:00.000Z'
                next_cursor: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
  '/v1/invoices/{id}':
    get:
      tags:
      - 'Invoices'
      operationId: 'getInvoice'
      summary: 'Fetch one invoice'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Invoice UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Invoice.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Invoice'
              example:
                id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                public_id: 'V7HQ3M2X9P'
                status: 'pending'
                amount_ton: '2.5'
                amount_nanoton: '2500000000'
                asset: 'TON'
                description: 'Order #42'
                payload_text: 'tp-V7HQ3M2X9P'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                ton_link: 'ton://transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                tonkeeper_link: 'https://app.tonkeeper.com/transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                public_url: 'https://tonpay.y7v.lol/p/7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                expires_at: '2026-05-24T13:05:00.000Z'
                paid_at: null
                paid_tx_hash: null
                paid_amount_nanoton: null
                paid_from_address: null
                metadata:
                  order_id: '42'
                callback_url: null
                created_at: '2026-05-24T12:05:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/invoices/{id}/cancel':
    post:
      tags:
      - 'Invoices'
      operationId: 'cancelInvoice'
      summary: 'Cancel a pending invoice'
      description: 'Only `pending` invoices can be cancelled — others return 409.'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Invoice UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Cancelled invoice.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Invoice'
              example:
                id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                public_id: 'V7HQ3M2X9P'
                status: 'cancelled'
                amount_ton: '2.5'
                amount_nanoton: '2500000000'
                asset: 'TON'
                description: 'Order #42'
                payload_text: 'tp-V7HQ3M2X9P'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                ton_link: 'ton://transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                tonkeeper_link: 'https://app.tonkeeper.com/transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                public_url: 'https://tonpay.y7v.lol/p/7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                expires_at: '2026-05-24T13:05:00.000Z'
                paid_at: null
                paid_tx_hash: null
                paid_amount_nanoton: null
                paid_from_address: null
                metadata:
                  order_id: '42'
                callback_url: null
                created_at: '2026-05-24T12:05:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '409':
          description: 'cannot cancel invoice in status paid'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'conflict'
                  message: 'cannot cancel invoice in status paid'
  '/v1/invoices/{id}/public':
    get:
      tags:
      - 'Invoices'
      operationId: 'getPublicInvoice'
      summary: 'Public checkout view of an invoice'
      description: 'Unauthenticated; rate-limited to 60 req / min / IP. Used by the public `/p/:id` page.'
      security: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Invoice UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Public invoice.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/PublicInvoice'
              example:
                id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                status: 'pending'
                amount_ton: '2.5'
                amount_nanoton: '2500000000'
                asset: 'TON'
                description: 'Order #42'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                payload_text: 'tp-V7HQ3M2X9P'
                ton_link: 'ton://transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                tonkeeper_link: 'https://app.tonkeeper.com/transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                expires_at: '2026-05-24T13:05:00.000Z'
                paid_at: null
                paid_tx_hash: null
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '429':
          description: 'Too many requests.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'rate_limited'
                  message: 'Too many requests.'
  '/v1/invoices/{id}/qr.svg':
    get:
      tags:
      - 'Invoices'
      operationId: 'getInvoiceQrSvg'
      summary: 'Invoice QR code (SVG)'
      description: 'Rate-limited to 60 req / min / IP. Encodes the TON deep link with amount and payload text.'
      security: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Invoice UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'SVG QR code.'
          content:
            'image/svg+xml':
              schema:
                type: 'string'
                format: 'binary'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '429':
          description: 'Too many requests.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'rate_limited'
                  message: 'Too many requests.'
        '503':
          description: 'Wallet not set'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'wallet_not_configured'
                  message: 'Wallet not set'
  '/v1/invoices/{id}/qr.png':
    get:
      tags:
      - 'Invoices'
      operationId: 'getInvoiceQrPng'
      summary: 'Invoice QR code (PNG)'
      description: 'Rate-limited to 60 req / min / IP. Returns a 512×512 PNG image.'
      security: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Invoice UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'PNG QR code.'
          content:
            image/png:
              schema:
                type: 'string'
                format: 'binary'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '429':
          description: 'Too many requests.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'rate_limited'
                  message: 'Too many requests.'
        '503':
          description: 'Wallet not set'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'wallet_not_configured'
                  message: 'Wallet not set'
  '/v1/invoices/{public_id}/events':
    get:
      tags:
      - 'Invoices'
      operationId: 'streamInvoiceEvents'
      summary: 'Server-Sent Events stream for invoice updates'
      description: 'Returns `text/event-stream`. Pushes a JSON event whenever the invoice transitions state (`paid`, `expired`, `cancelled`) or every 25 s as a keepalive. Use the public_id (10-char short form) so the URL is safe to embed in a checkout page.'
      security: []
      parameters:
      - name: 'public_id'
        in: 'path'
        required: true
        description: '10-character public invoice ID.'
        schema:
          type: 'string'
          pattern: '^[0-9A-Z]{10}$'
          minLength: 10
          maxLength: 10
        example: 'V7HQ3M2X9P'
      responses:
        '200':
          description: 'SSE stream — JSON events.'
          content:
            text/event-stream:
              schema:
                type: 'string'
              example: 'event: invoice.update
data: {"id":"7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c","status":"paid"}

'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/webhooks':
    post:
      tags:
      - 'Webhooks'
      operationId: 'createWebhook'
      summary: 'Register a webhook endpoint'
      description: 'On creation the API returns the signing secret **once** — store it. All deliveries include `X-Tonpay-Signature` HMAC-SHA256 over the raw body.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'url'
              properties:
                url:
                  type: 'string'
                  format: 'uri'
                  maxLength: 2048
                events:
                  type: 'array'
                  items:
                    type: 'string'
                    minLength: 1
                    maxLength: 64
                  minItems: 1
                  maxItems: 16
                  example:
                  - 'invoice.paid'
                  - 'invoice.expired'
            example:
              url: 'https://example.com/tonpay/webhook'
              events:
              - 'invoice.paid'
      responses:
        '200':
          description: 'Webhook created. `signing_secret` is shown once.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Webhook'
              example:
                id: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                url: 'https://example.com/tonpay/webhook'
                events:
                - 'invoice.paid'
                signing_secret: 'whsec_1a2b3c4d5e6f7g8h9i0j'
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
    get:
      tags:
      - 'Webhooks'
      operationId: 'listWebhooks'
      summary: 'List webhook endpoints'
      responses:
        '200':
          description: 'Webhooks.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      '$ref': '#/components/schemas/Webhook'
              example:
                data:
                - id: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  url: 'https://example.com/tonpay/webhook'
                  events:
                  - 'invoice.paid'
                  disabled: false
                  created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
  '/v1/webhooks/{id}':
    get:
      tags:
      - 'Webhooks'
      operationId: 'getWebhook'
      summary: 'Fetch one webhook'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Webhook UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Webhook.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Webhook'
              example:
                id: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                url: 'https://example.com/tonpay/webhook'
                events:
                - 'invoice.paid'
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
    patch:
      tags:
      - 'Webhooks'
      operationId: 'updateWebhook'
      summary: 'Update webhook URL / events / enabled state'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Webhook UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              properties:
                url:
                  type: 'string'
                  format: 'uri'
                  maxLength: 2048
                events:
                  type: 'array'
                  items:
                    type: 'string'
                  minItems: 1
                  maxItems: 16
                disabled:
                  type: 'boolean'
            example:
              events:
              - 'invoice.paid'
              - 'invoice.expired'
      responses:
        '200':
          description: 'Updated webhook.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Webhook'
              example:
                id: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                url: 'https://example.com/tonpay/webhook'
                events:
                - 'invoice.paid'
                - 'invoice.expired'
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
    delete:
      tags:
      - 'Webhooks'
      operationId: 'deleteWebhook'
      summary: 'Delete a webhook'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Webhook UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '204':
          description: 'Deleted.'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/webhooks/{id}/test':
    post:
      tags:
      - 'Webhooks'
      operationId: 'testWebhook'
      summary: 'Enqueue a synthetic delivery'
      description: 'Sends a fake event to the configured URL so you can verify signature handling.'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Webhook UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: 'object'
              properties:
                event:
                  type: 'string'
                  minLength: 1
                  maxLength: 64
            example:
              event: 'webhook.test'
      responses:
        '200':
          description: 'Delivery enqueued.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                - 'delivery_id'
                properties:
                  delivery_id:
                    type: 'string'
                    format: 'uuid'
              example:
                delivery_id: '11111111-2222-3333-4444-555555555555'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/webhooks/{id}/attempts':
    get:
      tags:
      - 'Webhooks'
      operationId: 'listWebhookAttempts'
      summary: 'Delivery attempts for one webhook'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Webhook UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 50
      responses:
        '200':
          description: 'Recent delivery attempts.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      type: 'object'
                      additionalProperties: true
              example:
                data:
                - id: '22222222-2222-3333-4444-555555555555'
                  webhook_id: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  invoice_id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  event: 'invoice.paid'
                  attempt_no: 1
                  status_code: 200
                  response_body_snippet: 'ok'
                  error: null
                  next_retry_at: null
                  delivered_at: '2026-05-24T12:01:00.000Z'
                  delivery_id: '11111111-2222-3333-4444-555555555555'
                  created_at: '2026-05-24T12:01:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/webhooks/rotate':
    post:
      tags:
      - 'Webhooks'
      operationId: 'rotateWebhookSecret'
      summary: 'Rotate a webhook signing secret'
      description: 'Generates a new signing secret for an existing webhook and returns it **once**. Update your consumer before the old secret expires (typically a 5 minute overlap is allowed).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'webhook_id'
              properties:
                webhook_id:
                  type: 'string'
                  format: 'uuid'
            example:
              webhook_id: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'New secret returned (only this once).'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Webhook'
              example:
                id: 'c4a4ec02-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                url: 'https://example.com/tonpay/webhook'
                events:
                - 'invoice.paid'
                signing_secret: 'whsec_NEW_a1b2c3d4e5f6'
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/api-keys':
    post:
      tags:
      - 'ApiKeys'
      operationId: 'createApiKey'
      summary: 'Issue an API key'
      description: 'The full secret (`key`) is returned **only once** in this response. Store it immediately.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'name'
              properties:
                name:
                  type: 'string'
                  minLength: 1
                  maxLength: 64
            example:
              name: 'production-server-1'
      responses:
        '200':
          description: 'Created key.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/ApiKey'
              example:
                id: 'd4a4ec03-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                prefix: 'tp_live_94b7e8f0'
                key: 'tp_live_94b7e8f0a1d24c9b8e6f3d2a7c1b9e0d'
                name: 'production-server-1'
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
    get:
      tags:
      - 'ApiKeys'
      operationId: 'listApiKeys'
      summary: 'List API keys'
      description: 'Secrets are never returned — only metadata.'
      responses:
        '200':
          description: 'Your API keys.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      '$ref': '#/components/schemas/ApiKey'
              example:
                data:
                - id: 'd4a4ec03-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  prefix: 'tp_live_94b7e8f0'
                  name: 'production-server-1'
                  last_used_at: '2026-05-24T11:59:00.000Z'
                  revoked_at: null
                  created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
  '/v1/api-keys/{id}':
    delete:
      tags:
      - 'ApiKeys'
      operationId: 'deleteApiKey'
      summary: 'Revoke an API key'
      description: 'Permanently invalidates the key — all subsequent requests with it return 401.'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'API key UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'd4a4ec03-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '204':
          description: 'Revoked.'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/api-keys/rotate':
    post:
      tags:
      - 'ApiKeys'
      operationId: 'rotateApiKey'
      summary: 'Rotate an API key'
      description: 'Creates a fresh secret for an existing key entry, keeping `id`/`name` stable. The previous secret continues to work for a 5 minute grace window so you can deploy the new value safely.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'api_key_id'
              properties:
                api_key_id:
                  type: 'string'
                  format: 'uuid'
            example:
              api_key_id: 'd4a4ec03-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Rotated key — store the new secret.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/ApiKey'
              example:
                id: 'd4a4ec03-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                prefix: 'tp_live_NEW123ab'
                key: 'tp_live_NEW123ab94b7e8f0a1d24c9b8e6f3d2a'
                name: 'production-server-1'
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/balance':
    get:
      tags:
      - 'Balance'
      operationId: 'getBalance'
      summary: 'Get current balance'
      description: 'Returns `available` (withdrawable), `pending` (locked by in-flight withdrawals) and `total_received` (lifetime gross). All amounts are nano-TON strings.'
      responses:
        '200':
          description: 'Balance snapshot.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Balance'
              example:
                available_nanoton: '12500000000'
                pending_nanoton: '0'
                total_received_nanoton: '57500000000'
                commission_bps: 100
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
  '/v1/withdrawals':
    post:
      tags:
      - 'Withdrawals'
      operationId: 'createWithdrawal'
      summary: 'Request a withdrawal'
      description: 'Queues a TON transfer from your platform balance to an external wallet. The withdrawal is created in `pending`, moves to `queued` once the operator picks it up, then `sent` → `confirmed` (or `failed`).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'to_address'
              - 'amount_nanoton'
              properties:
                to_address:
                  type: 'string'
                  description: 'Destination TON address. Must be base64url or raw form.'
                  minLength: 48
                  maxLength: 68
                  example: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                amount_nanoton:
                  type: 'string'
                  description: 'Always stringified. Min 100000000 (0.1 TON).'
                  pattern: '^[0-9]+$'
                  example: '1500000000'
                comment:
                  type: 'string'
                  maxLength: 120
                  example: 'payout'
            example:
              to_address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
              amount_nanoton: '1500000000'
              comment: 'payout'
      responses:
        '200':
          description: 'Withdrawal queued.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Withdrawal'
              example:
                id: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                status: 'pending'
                to_address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                amount_nanoton: '1500000000'
                fee_nanoton: '5000000'
                net_nanoton: '1495000000'
                comment: 'payout'
                tx_hash: null
                failure_reason: null
                requested_at: '2026-05-24T12:05:00.000Z'
                sent_at: null
                confirmed_at: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '409':
          description: 'available balance is below the requested amount'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'insufficient_funds'
                  message: 'available balance is below the requested amount'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
    get:
      tags:
      - 'Withdrawals'
      operationId: 'listWithdrawals'
      summary: 'List withdrawals'
      parameters:
      - name: 'status'
        in: 'query'
        schema:
          type: 'string'
          enum:
          - 'pending'
          - 'queued'
          - 'sent'
          - 'confirmed'
          - 'failed'
      - name: 'cursor'
        in: 'query'
        schema:
          type: 'string'
          maxLength: 64
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 20
      responses:
        '200':
          description: 'Paginated withdrawals.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      '$ref': '#/components/schemas/Withdrawal'
                  next_cursor:
                    type:
                    - 'string'
                    - 'null'
              example:
                data:
                - id: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  status: 'pending'
                  to_address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                  amount_nanoton: '1500000000'
                  fee_nanoton: '5000000'
                  net_nanoton: '1495000000'
                  comment: 'payout'
                  tx_hash: null
                  failure_reason: null
                  requested_at: '2026-05-24T12:05:00.000Z'
                  sent_at: null
                  confirmed_at: null
                next_cursor: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
  '/v1/withdrawals/{id}':
    get:
      tags:
      - 'Withdrawals'
      operationId: 'getWithdrawal'
      summary: 'Fetch one withdrawal'
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Withdrawal UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Withdrawal.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Withdrawal'
              example:
                id: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                status: 'pending'
                to_address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                amount_nanoton: '1500000000'
                fee_nanoton: '5000000'
                net_nanoton: '1495000000'
                comment: 'payout'
                tx_hash: null
                failure_reason: null
                requested_at: '2026-05-24T12:05:00.000Z'
                sent_at: null
                confirmed_at: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/admin/users':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListUsers'
      summary: 'List all users'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'cursor'
        in: 'query'
        schema:
          type: 'string'
          maxLength: 64
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 20
      responses:
        '200':
          description: 'Paginated users.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      type: 'object'
                      additionalProperties: true
                  next_cursor:
                    type:
                    - 'string'
                    - 'null'
              example:
                data:
                - id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  telegram_username: 'satoshi'
                  is_admin: false
                  disabled: false
                  created_at: '2026-05-24T12:00:00.000Z'
                  updated_at: '2026-05-24T12:00:00.000Z'
                next_cursor: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/users/{id}':
    patch:
      tags:
      - 'Admin'
      operationId: 'adminPatchUser'
      summary: 'Patch a user (disabled, is_admin, note)'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'User UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              properties:
                disabled:
                  type: 'boolean'
                is_admin:
                  type: 'boolean'
                note:
                  type:
                  - 'string'
                  - 'null'
                  maxLength: 1024
            example:
              disabled: true
      responses:
        '200':
          description: 'Updated user (admin shape).'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                telegram_username: 'satoshi'
                is_admin: false
                disabled: true
                created_at: '2026-05-24T12:00:00.000Z'
                updated_at: '2026-05-24T12:00:00.000Z'
        '400':
          description: 'no fields to update'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'no fields to update'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/admin/users/{id}/disable':
    post:
      tags:
      - 'Admin'
      operationId: 'adminDisableUser'
      summary: 'Disable a user'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'User UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Disabled.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                disabled: true
                is_admin: false
                telegram_username: null
                created_at: '2026-05-24T12:00:00.000Z'
                updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/admin/users/{id}/enable':
    post:
      tags:
      - 'Admin'
      operationId: 'adminEnableUser'
      summary: 'Re-enable a user'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'User UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Enabled.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                disabled: false
                is_admin: false
                telegram_username: null
                created_at: '2026-05-24T12:00:00.000Z'
                updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/admin/users/{id}/commission':
    patch:
      tags:
      - 'Admin'
      operationId: 'adminSetUserCommission'
      summary: 'Override commission for one user'
      description: 'Sets a per-user override in basis points. Pass `null` to restore the platform default.'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'User UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'commission_bps'
              properties:
                commission_bps:
                  type:
                  - 'integer'
                  - 'null'
                  minimum: 0
                  maximum: 10000
            example:
              commission_bps: 50
      responses:
        '200':
          description: 'Commission updated.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                id: 'b4a4ec01-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                commission_bps: 50
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
  '/admin/wallets':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListWallets'
      summary: 'List configured wallets'
      security:
      - accountKey: []
      - bearer: []
      responses:
        '200':
          description: 'Wallets.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      '$ref': '#/components/schemas/Wallet'
              example:
                data:
                - id: 'e4a4ec04-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                  label: 'default'
                  version: 'v4r2'
                  workchain: 0
                  disabled: false
                  created_at: '2026-05-24T12:00:00.000Z'
                  updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
    post:
      tags:
      - 'Admin'
      operationId: 'adminSetWallet'
      summary: 'Configure the receive wallet from a mnemonic'
      description: 'Encrypts the 24-word mnemonic at rest and derives the public address.'
      security:
      - accountKey: []
      - bearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'mnemonic'
              properties:
                mnemonic:
                  type: 'string'
                  description: '24 space-separated words'
                workchain:
                  type: 'integer'
                  minimum: -1
                  maximum: 1
                  default: 0
                version:
                  type: 'string'
                  enum:
                  - 'v4r2'
                  - 'v5r1'
                  default: 'v4r2'
                label:
                  type: 'string'
                  maxLength: 64
                  default: 'default'
            example:
              mnemonic: 'word1 word2 ... word24'
              workchain: 0
              version: 'v4r2'
              label: 'default'
      responses:
        '200':
          description: 'Wallet configured.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                id: 'e4a4ec04-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                label: 'default'
                version: 'v4r2'
                workchain: 0
                created_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
  '/admin/wallets/generate':
    post:
      tags:
      - 'Admin'
      operationId: 'adminGenerateWallet'
      summary: 'Generate a brand-new wallet on the server'
      description: 'The platform creates a fresh 24-word mnemonic, derives the address and stores the encrypted seed. The mnemonic is **not** returned in the response — use `POST /admin/wallets/{id}/disclose` to retrieve it once.'
      security:
      - accountKey: []
      - bearer: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: 'object'
              properties:
                workchain:
                  type: 'integer'
                  minimum: -1
                  maximum: 1
                  default: 0
                version:
                  type: 'string'
                  enum:
                  - 'v4r2'
                  - 'v5r1'
                  default: 'v4r2'
                label:
                  type: 'string'
                  maxLength: 64
                  default: 'generated'
            example:
              version: 'v4r2'
              label: 'cold-storage'
      responses:
        '200':
          description: 'Wallet created.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Wallet'
              example:
                id: 'e4a4ec04-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                label: 'cold-storage'
                version: 'v4r2'
                workchain: 0
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
                updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/wallets/{id}/disclose':
    post:
      tags:
      - 'Admin'
      operationId: 'adminDiscloseWallet'
      summary: 'Reveal a wallet mnemonic (once)'
      description: 'Decrypts and returns the 24-word mnemonic for a wallet. Audited heavily. Returns 409 once the disclosure flag has already been consumed.'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Wallet UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'e4a4ec04-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Wallet mnemonic.'
          content:
            application/json:
              schema:
                type: 'object'
                required:
                - 'mnemonic'
                properties:
                  mnemonic:
                    type: 'string'
              example:
                mnemonic: 'word1 word2 ... word24'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '409':
          description: 'mnemonic already disclosed'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'already_disclosed'
                  message: 'mnemonic already disclosed'
  '/admin/wallets/{id}/balance':
    get:
      tags:
      - 'Admin'
      operationId: 'adminGetWalletBalance'
      summary: 'Live on-chain wallet balance'
      description: 'Queries the TON network for the current balance of the wallet address.'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Wallet UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'e4a4ec04-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Balance.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  address:
                    type: 'string'
                  balance_nanoton:
                    type: 'string'
                  queried_at:
                    type: 'string'
                    format: 'date-time'
              example:
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                balance_nanoton: '12500000000'
                queried_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/admin/invoices':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListInvoices'
      summary: 'List all invoices (any user)'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'user_id'
        in: 'query'
        schema:
          type: 'string'
          format: 'uuid'
      - name: 'status'
        in: 'query'
        schema:
          type: 'string'
          enum:
          - 'pending'
          - 'paid'
          - 'expired'
          - 'cancelled'
      - name: 'cursor'
        in: 'query'
        schema:
          type: 'string'
          maxLength: 64
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 20
      responses:
        '200':
          description: 'Paginated invoices.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      '$ref': '#/components/schemas/Invoice'
                  next_cursor:
                    type:
                    - 'string'
                    - 'null'
              example:
                data:
                - id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  public_id: 'V7HQ3M2X9P'
                  status: 'pending'
                  amount_ton: '2.5'
                  amount_nanoton: '2500000000'
                  asset: 'TON'
                  description: 'Order #42'
                  payload_text: 'tp-V7HQ3M2X9P'
                  address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                  ton_link: 'ton://transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                  tonkeeper_link: 'https://app.tonkeeper.com/transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                  public_url: 'https://tonpay.y7v.lol/p/7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  expires_at: '2026-05-24T13:05:00.000Z'
                  paid_at: null
                  paid_tx_hash: null
                  paid_amount_nanoton: null
                  paid_from_address: null
                  metadata:
                    order_id: '42'
                  callback_url: null
                  created_at: '2026-05-24T12:05:00.000Z'
                next_cursor: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/invoices/{id}/cancel':
    post:
      tags:
      - 'Admin'
      operationId: 'adminCancelInvoice'
      summary: 'Force-cancel an invoice'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Invoice UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Cancelled.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Invoice'
              example:
                id: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                public_id: 'V7HQ3M2X9P'
                status: 'cancelled'
                amount_ton: '2.5'
                amount_nanoton: '2500000000'
                asset: 'TON'
                description: 'Order #42'
                payload_text: 'tp-V7HQ3M2X9P'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                ton_link: 'ton://transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                tonkeeper_link: 'https://app.tonkeeper.com/transfer/UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y?amount=2500000000&text=tp-V7HQ3M2X9P'
                public_url: 'https://tonpay.y7v.lol/p/7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                expires_at: '2026-05-24T13:05:00.000Z'
                paid_at: null
                paid_tx_hash: null
                paid_amount_nanoton: null
                paid_from_address: null
                metadata:
                  order_id: '42'
                callback_url: null
                created_at: '2026-05-24T12:05:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '409':
          description: 'cannot cancel invoice in status paid'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'conflict'
                  message: 'cannot cancel invoice in status paid'
  '/admin/withdrawals':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListWithdrawals'
      summary: 'List all withdrawals (any user)'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'status'
        in: 'query'
        schema:
          type: 'string'
          enum:
          - 'pending'
          - 'queued'
          - 'sent'
          - 'confirmed'
          - 'failed'
      - name: 'user_id'
        in: 'query'
        schema:
          type: 'string'
          format: 'uuid'
      - name: 'cursor'
        in: 'query'
        schema:
          type: 'string'
          maxLength: 64
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 20
      responses:
        '200':
          description: 'Paginated withdrawals.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      '$ref': '#/components/schemas/Withdrawal'
                  next_cursor:
                    type:
                    - 'string'
                    - 'null'
              example:
                data:
                - id: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                  status: 'pending'
                  to_address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                  amount_nanoton: '1500000000'
                  fee_nanoton: '5000000'
                  net_nanoton: '1495000000'
                  comment: 'payout'
                  tx_hash: null
                  failure_reason: null
                  requested_at: '2026-05-24T12:05:00.000Z'
                  sent_at: null
                  confirmed_at: null
                next_cursor: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/withdrawals/{id}/force-fail':
    post:
      tags:
      - 'Admin'
      operationId: 'adminForceFailWithdrawal'
      summary: 'Force a withdrawal into the `failed` state'
      description: 'Operator-only escape hatch — moves a stuck withdrawal to `failed` and refunds the available balance. Idempotent: 409 if already terminal.'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Withdrawal UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: 'object'
              properties:
                reason:
                  type: 'string'
                  maxLength: 512
            example:
              reason: 'manual operator decision'
      responses:
        '200':
          description: 'Updated.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Withdrawal'
              example:
                id: 'f4a4ec05-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                status: 'failed'
                to_address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                amount_nanoton: '1500000000'
                fee_nanoton: '5000000'
                net_nanoton: '1495000000'
                comment: 'payout'
                tx_hash: null
                failure_reason: 'manual operator decision'
                requested_at: '2026-05-24T12:05:00.000Z'
                sent_at: null
                confirmed_at: null
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
        '409':
          description: 'withdrawal already in terminal state'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'conflict'
                  message: 'withdrawal already in terminal state'
  '/admin/webhooks/attempts':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListWebhookAttempts'
      summary: 'Recent webhook delivery attempts'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'invoice_id'
        in: 'query'
        schema:
          type: 'string'
          format: 'uuid'
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 20
      responses:
        '200':
          description: 'Attempts.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      type: 'object'
                      additionalProperties: true
              example:
                data: []
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/webhooks/attempts/{id}/replay':
    post:
      tags:
      - 'Admin'
      operationId: 'adminReplayWebhookAttempt'
      summary: 'Re-enqueue a webhook attempt'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Webhook attempt UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Replayed.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  delivery_id:
                    type: 'string'
                    format: 'uuid'
              example:
                delivery_id: '11111111-2222-3333-4444-555555555555'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/admin/audit-logs':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListAuditLogs'
      summary: 'Read audit logs'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 20
      - name: 'since'
        in: 'query'
        schema:
          type: 'string'
          format: 'date-time'
      responses:
        '200':
          description: 'Audit log entries.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      type: 'object'
                      additionalProperties: true
              example:
                data: []
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/settings':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListSettings'
      summary: 'Read all system settings'
      security:
      - accountKey: []
      - bearer: []
      responses:
        '200':
          description: 'Settings rows.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      type: 'object'
                      additionalProperties: true
              example:
                data:
                - key: 'commission_bps'
                  value: 100
                  updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
    patch:
      tags:
      - 'Admin'
      operationId: 'adminPatchSetting'
      summary: 'Upsert a system setting'
      security:
      - accountKey: []
      - bearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'key'
              - 'value'
              properties:
                key:
                  type: 'string'
                  minLength: 1
                  maxLength: 64
                value: {}
            example:
              key: 'commission_bps'
              value: 100
      responses:
        '200':
          description: 'Saved.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                key: 'commission_bps'
                value: 100
                updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
    post:
      tags:
      - 'Admin'
      operationId: 'adminPostSetting'
      summary: 'Upsert a system setting (POST alias)'
      description: 'Same as `PATCH /admin/settings`. Provided for web clients that prefer POST.'
      security:
      - accountKey: []
      - bearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: 'object'
              required:
              - 'key'
              - 'value'
              properties:
                key:
                  type: 'string'
                  minLength: 1
                  maxLength: 64
                value: {}
            example:
              key: 'commission_bps'
              value: 100
      responses:
        '200':
          description: 'Saved.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                key: 'commission_bps'
                value: 100
                updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '422':
          description: 'Request body failed validation.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'invalid_body'
                  message: 'Request body failed validation.'
  '/admin/health':
    get:
      tags:
      - 'Admin'
      operationId: 'adminGetHealth'
      summary: 'Detailed health (admin)'
      description: 'Like `/ready` but also reports wallet configuration and software version.'
      security:
      - accountKey: []
      - bearer: []
      responses:
        '200':
          description: 'Detailed health.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                db: 'ok'
                redis: 'ok'
                wallet_configured: true
                version: '0.1.0'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/overview':
    get:
      tags:
      - 'Admin'
      operationId: 'adminOverview'
      summary: 'Composite admin dashboard overview'
      security:
      - accountKey: []
      - bearer: []
      responses:
        '200':
          description: 'Aggregated metrics.'
          content:
            application/json:
              schema:
                type: 'object'
                additionalProperties: true
              example:
                users:
                  total: 100
                  active: 95
                invoices:
                  pending: 3
                  paid_30d: 42
                  paid_volume_30d_nanoton: '57500000000'
                wallet:
                  configured: true
                  address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                  version: 'v4r2'
                version: '0.1.0'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/analytics':
    get:
      tags:
      - 'Admin'
      operationId: 'adminAnalytics'
      summary: 'Daily analytics (1–90 days)'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'days'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 90
          default: 30
      responses:
        '200':
          description: 'Time series.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  days:
                    type: 'array'
                    items:
                      type: 'object'
                      properties:
                        date:
                          type: 'string'
                          example: '2026-05-23'
                        invoices_created:
                          type: 'integer'
                        invoices_paid:
                          type: 'integer'
                        paid_volume_nanoton:
                          type: 'string'
              example:
                days:
                - date: '2026-05-23'
                  invoices_created: 12
                  invoices_paid: 9
                  paid_volume_nanoton: '22500000000'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/audit':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListAuditLogsAlias'
      summary: 'Alias for /admin/audit-logs'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 50
      - name: 'since'
        in: 'query'
        schema:
          type: 'string'
          format: 'date-time'
      responses:
        '200':
          description: 'Audit entries.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      type: 'object'
                      additionalProperties: true
              example:
                data: []
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/webhook-attempts':
    get:
      tags:
      - 'Admin'
      operationId: 'adminListWebhookAttemptsAlias'
      summary: 'Alias for /admin/webhooks/attempts (also supports webhook_id filter)'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'invoice_id'
        in: 'query'
        schema:
          type: 'string'
          format: 'uuid'
      - name: 'webhook_id'
        in: 'query'
        schema:
          type: 'string'
          format: 'uuid'
      - name: 'limit'
        in: 'query'
        schema:
          type: 'integer'
          minimum: 1
          maximum: 100
          default: 50
      responses:
        '200':
          description: 'Attempts.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  data:
                    type: 'array'
                    items:
                      type: 'object'
                      additionalProperties: true
              example:
                data: []
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
  '/admin/webhook-attempts/{id}/replay':
    post:
      tags:
      - 'Admin'
      operationId: 'adminReplayWebhookAttemptAlias'
      summary: 'Alias for /admin/webhooks/attempts/{id}/replay'
      security:
      - accountKey: []
      - bearer: []
      parameters:
      - name: 'id'
        in: 'path'
        required: true
        description: 'Webhook attempt UUID.'
        schema:
          type: 'string'
          format: 'uuid'
          pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
        example: '7a4a4ec0-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
      responses:
        '200':
          description: 'Replayed.'
          content:
            application/json:
              schema:
                type: 'object'
                properties:
                  delivery_id:
                    type: 'string'
                    format: 'uuid'
              example:
                delivery_id: '11111111-2222-3333-4444-555555555555'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '403':
          description: 'Caller is not allowed to perform this action.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'forbidden'
                  message: 'Caller is not allowed to perform this action.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
  '/v1/wallet':
    get:
      tags:
      - 'Accounts'
      operationId: 'getWallet'
      summary: 'Read-only view of the receive wallet'
      description: 'Returns the public wallet metadata (no mnemonic, no private keys).'
      responses:
        '200':
          description: 'Wallet (public shape).'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Wallet'
              example:
                id: 'e4a4ec04-3b5d-4a1d-9e2a-2c2c2c2c2c2c'
                address: 'UQA1Lcyo6oXgvkLpYxQyqg0Y_xLZkX5b9Zk6DnK1zCmM9k1y'
                label: 'default'
                version: 'v4r2'
                workchain: 0
                disabled: false
                created_at: '2026-05-24T12:00:00.000Z'
                updated_at: '2026-05-24T12:00:00.000Z'
        '401':
          description: 'Authentication required.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'unauthorized'
                  message: 'Authentication required.'
        '404':
          description: 'Resource does not exist.'
          content:
            application/json:
              schema:
                '$ref': '#/components/schemas/Error'
              example:
                error:
                  code: 'not_found'
                  message: 'Resource does not exist.'
