{
  "openapi": "3.1.0",
  "info": {
    "title": "CryptoPSP API",
    "version": "0.5.0",
    "description": "Two isolated installations use the same API: https://cryptopsp.trade is live/mainnet; https://sandbox.cryptopsp.trade is sandbox/testnet. Accounts, keys, wallets, balances, events and audit history are separate. Both network and currency are required for payment creation, payout creation and balance reads. Supported pairs are ethereum/USDT, ethereum/USDC, tron/USDT, solana/USDT and solana/USDC. Use GET /v1/payment-methods to discover the authenticated company's enabled methods grouped by stablecoin; do not assume every supported pair is connected. The host fixes the environment; callers cannot select arbitrary token contracts or mints. All supported stablecoins use six decimals.\n\nThe unified Wallets portal accepts named wallets by network and verified private key, derives the address, and enables supported stablecoins with checkboxes. Each company/network/currency has one active wallet binding, enforced atomically; one wallet may accept multiple tokens. Reassignment affects new operations, while existing deposit and prepared transaction records retain their original wallet. Old holdings are not moved or imported automatically.\n\nLive uses canonical issuer tokens on Ethereum, TRON and Solana mainnet. Sandbox uses Shasta, Ethereum Sepolia and Solana devnet. Sandbox USDC is Circle's published test token; USDT on Sepolia/devnet is a dedicated configured mock token, never relabeled USDC. Unconfigured test tokens are absent from available methods. Inspect the returned token_address before funding. Gas is paid in TRX, ETH or SOL respectively. Solana transfers use the standard SPL Token program, not Token-2022, and can create the recipient associated token account idempotently; the sender pays its SOL rent and transaction fee.\n\nEach invoice has a unique deposit owner address. Successful deposits and payouts require TRON solid-block or Ethereum/Solana finalized confirmation. Inclusion or a transaction ID alone is insufficient. Solana signed transactions have a bounded blockhash lifetime; expired or uncertain intents require signature reconciliation and can enter an operator needs_attention stage. They are never silently re-signed with a fresh blockhash. A payout reservation remains held until a definite outcome. Automatic fee schedules and on-chain fee settlement remain unavailable.\n\nAuthentication: Authorization: Bearer psp_live_… on live or psp_test_… on sandbox. Keys are bound to their installation. The key is displayed only once in the portal and may have expires_at=null; expired or revoked keys are rejected. integration grants all listed permissions; payments grants read access, payments:write, checkout:write, and simulation:write; read_only grants read access only. These are API key presets, not OAuth scopes.\n\nAll POST JSON API requests require Idempotency-Key (8–128 characters: A–Z, a–z, 0–9, _, ., :, -). Keys are scoped to the company and operation/resource. The same key and normalized DTO return 200 and Idempotent-Replayed:true without repeating the effect; a different body returns 409. The first execution returns 201. A replay returns the current representation of the original resource, not a stored byte-for-byte HTTP response. After an ambiguous network outcome, retry with the same key.\n\nX-Operation-ID and X-Request-ID are generated by the PSP for each HTTP call; incoming values are not accepted as trusted context. The resource's origin_operation_id remains unchanged. Monetary amounts are USDT or USDC strings with up to 6 decimal places. The fee for a confirmed incoming transfer is floor(amount_units × fee_bps / 10000) units of 0.000001 of the selected token; each transfer's fee is rounded down individually. Direct confirmed blockchain funding of the company wallet does not accrue invoice fees; historical virtual top-ups are available only in simulation mode.\n\nEach webhook event is stored and enqueued in Doctrine Messenger atomically with the change. Delivery follows at-least-once semantics: failures may cause duplicates, and delivery order is not guaranteed; the recipient deduplicates by event_id. There are up to 5 automatic retries after the initial attempt (1s, 4s, 16s, 64s, 256s), followed by the failure transport. Manual retry is available in the portal/API. The original raw body, URL, and encrypted secret are stored, so later configuration changes do not rewrite an existing event. A public HTTPS destination is required; the HTTP client checks DNS/IP addresses at delivery time, and redirects are disabled. WEBHOOK_ALLOWED_ORIGIN provides an explicit exception for a single origin only, for a local test receiver.\n\nSignature: CryptoPSP-Signature: t=<unix_seconds>,kid=primary,v1=<hex HMAC-SHA256(secret, timestamp + \".\" + raw_body)>. Verify the original bytes before parsing JSON, use a constant-time comparison, enforce an acceptable timestamp window, and check event_id; each retry has a fresh signature but the same body. The webhook secret is a separate secret from the settings, not the Bearer API key. Return 2xx only after durably storing the event. The current ErrorEnvelope contains only the listed fields; the extended details from the future design are not yet implemented.\n\nEthereum Payment reads include advisory chain_progress for transfers detected before final credit. Use it only for progress display; fulfillment still depends on the monetary Payment status and verified confirmation events. Reorganizations or delayed RPC scans can change observations without a payment version change."
  },
  "servers": [
    {
      "url": "/",
      "description": "Current installation. Use credentials issued on this host."
    },
    {
      "url": "https://cryptopsp.trade",
      "description": "Live: real USDT on TRON mainnet and USDC on Ethereum mainnet."
    },
    {
      "url": "https://sandbox.cryptopsp.trade",
      "description": "Sandbox: Shasta USDT and Sepolia USDC; isolated test funds and credentials."
    }
  ],
  "tags": [
    {
      "name": "Payments"
    },
    {
      "name": "Wallet"
    },
    {
      "name": "Payouts"
    },
    {
      "name": "Checkout"
    },
    {
      "name": "Events"
    },
    {
      "name": "Simulation"
    },
    {
      "name": "Runtime"
    }
  ],
  "security": [
    {
      "BearerApiKey": []
    }
  ],
  "paths": {
    "/v1/payments": {
      "get": {
        "operationId": "listPayments",
        "summary": "List the latest 100 payments",
        "description": "No pagination; the list is limited to the latest 100 records.\n\nRequired permission: `payments:read`.",
        "tags": [
          "Payments"
        ],
        "x-required-permission": "payments:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        }
      },
      "post": {
        "operationId": "createPayment",
        "summary": "Create a payment",
        "description": "Explicit network and currency are required: ethereum/USDT, ethereum/USDC, tron/USDT, solana/USDT or solana/USDC. The host determines live/mainnet versus sandbox/testnet; requests cannot override it. Minimum 10 units of the selected stablecoin; default expiry is 30 minutes. The company fee rate is fixed at creation. A connected wallet with the chosen network/currency binding is required for blockchain payments, and a unique deposit_address is returned. Optional return_url sets an immutable per-payment browser destination; otherwise checkout uses the company setting.\n\nRequired permission: `payments:write`. The response includes deposit_qr_code with the address payload and a PNG data URI. Display network_mode prominently, as the same address format is used on mainnet and testnet.",
        "tags": [
          "Payments"
        ],
        "x-required-permission": "payments:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePayment"
              },
              "example": {
                "external_id": "order-1001",
                "amount": "10.000000",
                "network": "ethereum",
                "currency": "USDC"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v1/payments/{id}": {
      "get": {
        "operationId": "getPayment",
        "summary": "Get a payment",
        "description": "Payment status is calculated from all confirmed incoming amounts regardless of expires_at. Unconfirmed amounts do not settle payments. Refunds are linked outgoing payouts and do not rewrite gross incoming amounts. Required permission: payments:read.",
        "tags": [
          "Payments"
        ],
        "x-required-permission": "payments:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PaymentId"
            }
          }
        ]
      }
    },
    "/sandbox/v1/payments/{id}/transfers": {
      "post": {
        "operationId": "simulateTransfer",
        "summary": "Simulate an incoming transfer",
        "description": "TRON simulation mode only. Blockchain payments and payouts cannot be simulated; use network transfers and automatic confirmation. All simulation operations are disabled on live. These operations never affect Ethereum or Solana balances.\n\nCreates a virtual transfer. With confirmed=true, immediately credits the confirmed amount and accrues the fee. included_at <= expires_at counts as on time even if confirmation occurs later. Partial amounts accumulate; overpayments are retained; a late transfer credits the wallet but does not count toward the payment amount received on time.\n\nRequired permission: `simulation:write`.",
        "tags": [
          "Simulation"
        ],
        "x-required-permission": "simulation:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulateTransfer"
              },
              "example": {
                "amount": "5.000000",
                "confirmed": true
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PaymentId"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v1/transfers/{id}": {
      "get": {
        "operationId": "getTransfer",
        "summary": "Get a transfer",
        "description": "Required permission: `payments:read`.",
        "tags": [
          "Payments"
        ],
        "x-required-permission": "payments:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/TransferId"
            }
          }
        ]
      }
    },
    "/sandbox/v1/transfers/{id}/confirm": {
      "post": {
        "operationId": "confirmTransfer",
        "summary": "Confirm a virtual transfer",
        "description": "TRON simulation mode only. Blockchain payments and payouts cannot be simulated; use network transfers and automatic confirmation. All simulation operations are disabled on live. These operations never affect Ethereum or Solana balances.\n\nApplies a previously unconfirmed transfer. A transfer that is already confirmed is not credited again.\n\nRequired permission: `simulation:write`.",
        "tags": [
          "Simulation"
        ],
        "x-required-permission": "simulation:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transfer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyInput"
              },
              "example": {}
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/TransferId"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v1/wallet": {
      "get": {
        "operationId": "getBalance",
        "summary": "Get the company wallet balance",
        "description": "Specify both network and currency, for example ?network=ethereum&currency=USDT. Supported pairs match the payment methods catalog. available = balance - reserved - fee_payable. Balances are isolated by company, network, currency and installation. Confirmed deposits and new managed-wallet funding credit only their own balance; pre-existing holdings are not imported. Historical simulation has a separate balance. Required permission: wallet:read.",
        "tags": [
          "Wallet"
        ],
        "x-required-permission": "wallet:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          }
        },
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "ethereum",
                "tron",
                "solana"
              ]
            },
            "description": "Blockchain network."
          },
          {
            "name": "currency",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "USDT",
                "USDC"
              ]
            },
            "description": "Stablecoin; TRON supports USDT only."
          }
        ]
      }
    },
    "/sandbox/v1/wallet/top-ups": {
      "post": {
        "operationId": "topUpWallet",
        "summary": "Top up the sandbox wallet without a fee",
        "description": "TRON simulation mode only. Blockchain payments and payouts cannot be simulated; use network transfers and automatic confirmation. All simulation operations are disabled on live. These operations never affect Ethereum or Solana balances.\n\nCredits a positive virtual amount without a fee. Does not create a blockchain transfer or webhook event.\n\nRequired permission: `wallet:write`.",
        "tags": [
          "Simulation"
        ],
        "x-required-permission": "wallet:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopUp"
              },
              "example": {
                "amount": "100.000000"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/sandbox/v1/wallet/fee-settlements": {
      "post": {
        "operationId": "settleFees",
        "summary": "Settle accrued virtual fees",
        "description": "TRON simulation mode only. Blockchain payments and payouts cannot be simulated; use network transfers and automatic confirmation. All simulation operations are disabled on live. These operations never affect Ethereum or Solana balances.\n\nReduces balance and fee_payable by the full accrued fee amount; available remains unchanged. A zero fee amount is allowed.\n\nRequired permission: `wallet:write`.",
        "tags": [
          "Simulation"
        ],
        "x-required-permission": "wallet:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyInput"
              },
              "example": {}
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v1/payouts": {
      "get": {
        "operationId": "listPayouts",
        "summary": "List the latest 100 payouts",
        "description": "Required permission: `wallet:read`.",
        "tags": [
          "Payouts"
        ],
        "x-required-permission": "wallet:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        }
      },
      "post": {
        "operationId": "createPayout",
        "summary": "Create and reserve a payout",
        "description": "Explicit network and currency are required: ethereum/USDT, ethereum/USDC, tron/USDT, solana/USDT or solana/USDC. The API host fixes the installation environment. Creates status accepted and reserves funds only in the selected balance. A worker signs and sends the stablecoin from the connected wallet. TRX (TRON), ETH (Ethereum) or SOL (Solana) is required for gas; Solana may also require rent for a new recipient token account. Processing may wait for liquidity or deposit collection. A successful TRON solid-block receipt or Ethereum/Solana finalized receipt confirms the payout. A transaction_id alone is not confirmation. Uncertain broadcasts remain reserved for reconciliation; retry creation with the original Idempotency-Key. Live requests move real funds. Manual completion is restricted to historical/local simulation records.\n\nRequired permission: `payouts:write`.",
        "tags": [
          "Payouts"
        ],
        "x-required-permission": "payouts:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePayout"
              },
              "example": {
                "external_id": "payout-1001",
                "amount": "20.000000",
                "destination": "0x1234567890abcdef1234567890abcdef12345678",
                "network": "ethereum",
                "currency": "USDC"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v1/payouts/{id}": {
      "get": {
        "operationId": "getPayout",
        "summary": "Get a payout",
        "description": "Required permission: `wallet:read`.",
        "tags": [
          "Payouts"
        ],
        "x-required-permission": "wallet:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PayoutId"
            }
          }
        ]
      }
    },
    "/sandbox/v1/payouts/{id}/complete": {
      "post": {
        "operationId": "completePayout",
        "summary": "Finalize a virtual payout",
        "description": "TRON simulation mode only. Blockchain payments and payouts cannot be simulated; use network transfers and automatic confirmation. All simulation operations are disabled on live. These operations never affect Ethereum or Solana balances.\n\nconfirmed deducts the amount from reserved and balance; failed releases the reserved amount back to available. Repeating the same final outcome does not repeat the effect; changing the final outcome returns 409.\n\nRequired permission: `payouts:write`.",
        "tags": [
          "Simulation"
        ],
        "x-required-permission": "payouts:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompletePayout"
              },
              "example": {
                "outcome": "confirmed"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PayoutId"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v1/checkout-sessions": {
      "post": {
        "operationId": "createCheckout",
        "summary": "Create a payment page",
        "description": "Links a checkout to an existing payment belonging to the same company. It expires at the same time as the payment. Browser return URL precedence: the Payment return_url override, then the current company setting. Branding and this effective URL are saved as a checkout snapshot and do not change later. Optional CreateCheckout.return_url must equal the effective URL; it cannot replace the payment override.\n\nRequired permission: `checkout:write`.",
        "tags": [
          "Checkout"
        ],
        "x-required-permission": "checkout:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckout"
              },
              "example": {
                "payment_id": "pay_11111111111111111111111111111111"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/v1/checkout-sessions/{id}": {
      "get": {
        "operationId": "getCheckout",
        "summary": "Get a checkout",
        "description": "Required permission: `payments:read`.",
        "tags": [
          "Checkout"
        ],
        "x-required-permission": "payments:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/CheckoutId"
            }
          }
        ]
      }
    },
    "/v1/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "List the latest 200 events",
        "description": "Recent company history: at most the latest 200 events, without a cursor or pagination. This is not a complete mechanism for reconciliation or recovery of the entire history. Events are stored even without a configured webhook; the raw body is not returned here.\n\nRequired permission: `events:read`.",
        "tags": [
          "Events"
        ],
        "x-required-permission": "events:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        }
      }
    },
    "/v1/events/{id}/retry": {
      "post": {
        "operationId": "retryEvent",
        "summary": "Enqueue a webhook retry",
        "description": "Reuses the stored URL, secret, and JSON. The original event_id/operation_id remain unchanged; the manual HTTP call receives a new operation_id and is linked to the event through the audit trail. An event without a stored webhook configuration cannot be retried. Different Idempotency-Key values can explicitly enqueue multiple retry deliveries.\n\nRequired permission: `payments:write`.",
        "tags": [
          "Events"
        ],
        "x-required-permission": "payments:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acknowledgement"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Acknowledgement"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyInput"
              },
              "example": {}
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/EventId"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/health/live": {
      "get": {
        "operationId": "liveness",
        "summary": "Check process liveness",
        "tags": [
          "Runtime"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Check passed.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "alive"
                  ]
                }
              }
            }
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        }
      }
    },
    "/health/ready": {
      "get": {
        "operationId": "readiness",
        "summary": "Check database and merchants table availability",
        "tags": [
          "Runtime"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Check passed.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "ready"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Database or table unavailable.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "unavailable"
                  ]
                }
              }
            }
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        }
      }
    },
    "/pay/{id}": {
      "get": {
        "operationId": "hostedCheckout",
        "summary": "Open the sandbox payment page",
        "tags": [
          "Checkout"
        ],
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/CheckoutId"
            }
          }
        ],
        "description": "Public HTML page accessed using an unpredictable checkout ID. Displays the payment amount, status, and saved branding. Blockchain payments include a unique deposit address and explicit network/token instructions. Mainnet payments clearly identify real funds. Simulation payments show a virtual reference. Never send mainnet funds to sandbox addresses.",
        "responses": {
          "200": {
            "description": "Payment page HTML.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        }
      }
    },
    "/v1/payment-methods": {
      "get": {
        "operationId": "getPaymentMethods",
        "summary": "List enabled payment methods grouped by stablecoin",
        "description": "Returns only supported, configured methods currently bound to a wallet of the authenticated company. No key material or wallet owner addresses are disclosed. Use currency first, then network in the payer UI. Methods can change after wallet reassignment; always handle creation-time validation. Listing is configuration-only and does not guarantee current gas or liquidity. Required permission: payments:read.",
        "tags": [
          "Payments"
        ],
        "x-required-permission": "payments:read",
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentMethods"
                },
                "example": {
                  "USDT": [
                    {
                      "network": "tron",
                      "standard": "TRC20",
                      "network_mode": "mainnet",
                      "token_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
                      "decimals": 6
                    }
                  ],
                  "USDC": [
                    {
                      "network": "solana",
                      "standard": "SPL",
                      "network_mode": "mainnet",
                      "token_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                      "decimals": 6
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        }
      }
    },
    "/v1/transfers/{id}/refunds": {
      "post": {
        "operationId": "createRefund",
        "summary": "Refund a confirmed incoming transfer",
        "description": "Requires payouts:write. Creates a linked payout to the verified original token sender, on the original network and asset. Recipient is server-selected. All non-failed refunds count toward the deposit cap, including pending/unknown outcomes. Known failed payouts free the cap; replaying their key returns the failed payout. Requires sufficient company available balance. Original processing fees are not reversed. Does not change gross Payment status. Rejects unknown or ambiguous senders. Idempotency and the cap are checked under the same merchant transaction lock.",
        "tags": [
          "Payouts"
        ],
        "x-required-permission": "payouts:write",
        "responses": {
          "200": {
            "description": "Idempotent replay; returns the current representation of the original resource.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "201": {
            "description": "First successful execution of the request.",
            "headers": {
              "X-Operation-ID": {
                "$ref": "#/components/headers/X-Operation-ID"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "500": {
            "$ref": "#/components/responses/Error500"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          },
          "405": {
            "description": "HTTP method not allowed; Allow header lists supported methods."
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON, up to 64 KiB; unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRefund"
              },
              "example": {
                "amount": "3.500000"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/TransferId"
            }
          }
        ]
      }
    }
  },
  "webhooks": {
    "eventDelivery": {
      "post": {
        "operationId": "receiveWebhookEvent",
        "summary": "Signed event sent to the stored webhook URL",
        "tags": [
          "Events"
        ],
        "security": [],
        "parameters": [
          {
            "name": "CryptoPSP-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=[0-9]+,kid=primary,v1=[a-f0-9]{64}$"
            }
          },
          {
            "name": "X-Operation-ID",
            "in": "header",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/OperationId"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "The recipient has durably stored the event; no retry is required."
          },
          "default": {
            "description": "Any non-2xx response, including a redirect, counts as a failed attempt; the PSP applies a bounded retry policy."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "psp_live_<64 hex characters> or psp_test_<64 hex characters>, matching the installation",
        "description": "Company API key, issued at /portal/keys."
      }
    },
    "headers": {
      "X-Operation-ID": {
        "description": "A new call identifier generated by the PSP. An incoming header of the same name does not set the PSP context.",
        "schema": {
          "$ref": "#/components/schemas/OperationId"
        }
      },
      "X-Request-ID": {
        "description": "HTTP request identifier.",
        "schema": {
          "type": "string",
          "pattern": "^req_[a-f0-9]{32}$"
        }
      },
      "Idempotent-Replayed": {
        "description": "true indicates a replay of the same idempotent request; the monetary effect was not applied a second time.",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ]
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_.:-]{8,128}$"
        },
        "description": "A stable key for one logical action; after a timeout, retry with the same key and an unchanged DTO."
      }
    },
    "responses": {
      "Error400": {
        "description": "Invalid JSON, fields, types, identifier, or Idempotency-Key.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error401": {
        "description": "Missing, invalid, expired, or revoked API key.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error403": {
        "description": "The API key has insufficient permissions.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error404": {
        "description": "The resource does not exist or belongs to another company.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error409": {
        "description": "Idempotency or state conflict, or insufficient available balance.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error422": {
        "description": "Invalid monetary amount or domain parameters.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error500": {
        "description": "Internal error; use error_id and operation_id to investigate.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error503": {
        "description": "Mandatory auditing is unavailable; the new action is not executed.",
        "headers": {
          "X-Operation-ID": {
            "$ref": "#/components/headers/X-Operation-ID"
          },
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "PaymentId": {
        "type": "string",
        "pattern": "^pay_[a-f0-9]{32}$",
        "examples": [
          "pay_11111111111111111111111111111111"
        ]
      },
      "TransferId": {
        "type": "string",
        "pattern": "^trf_[a-f0-9]{32}$",
        "examples": [
          "trf_11111111111111111111111111111111"
        ]
      },
      "PayoutId": {
        "type": "string",
        "pattern": "^pyo_[a-f0-9]{32}$",
        "examples": [
          "pyo_11111111111111111111111111111111"
        ]
      },
      "CheckoutId": {
        "type": "string",
        "pattern": "^chk_[a-f0-9]{32}$",
        "examples": [
          "chk_11111111111111111111111111111111"
        ]
      },
      "EventId": {
        "type": "string",
        "pattern": "^evt_[a-f0-9]{32}$",
        "examples": [
          "evt_11111111111111111111111111111111"
        ]
      },
      "OperationId": {
        "type": "string",
        "pattern": "^op_[a-f0-9]{32}$",
        "examples": [
          "op_11111111111111111111111111111111"
        ]
      },
      "Money": {
        "type": "string",
        "description": "USDT or USDC as a decimal string without a sign or exponent, with at most 12 integer digits and 6 fractional digits. Upper limit: 999999999999.999999.",
        "pattern": "^(0|[1-9][0-9]{0,11})(\\.[0-9]{1,6})?$",
        "examples": [
          "10.000000"
        ]
      },
      "MoneyOutput": {
        "type": "string",
        "description": "Amount in the resource currency; responses always contain six fractional digits.",
        "pattern": "^(0|[1-9][0-9]{0,11})\\.[0-9]{6}$",
        "examples": [
          "10.000000"
        ]
      },
      "CreatePayment": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "type": "string",
            "description": "Unique among the company's payments.",
            "minLength": 1,
            "maxLength": 128
          },
          "amount": {
            "$ref": "#/components/schemas/Money",
            "description": "Requested amount, minimum 10 units of the selected stablecoin."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Optional informational date only. Omitted or null remains null; no default or time-window restrictions. Does not affect payment status, crediting, checkout access or instructions. RFC 3339 whole seconds with timezone.",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$"
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 2048,
            "description": "Optional browser return URL for this payment. Overrides the company setting for every checkout of this payment. Use absolute HTTPS without credentials, whitespace, or backslashes; HTTP is accepted only for literal localhost, 127.0.0.1, or [::1] during local integration. Omitted/null uses the company setting when checkout is created. It never confirms payment."
          },
          "network": {
            "type": "string",
            "enum": [
              "ethereum",
              "tron",
              "solana"
            ],
            "description": "Required explicit blockchain network. The installation host determines mainnet versus testnet."
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC"
            ],
            "description": "Required explicit stablecoin. Must be enabled for this company/network. TRON supports USDT only."
          }
        },
        "required": [
          "external_id",
          "amount",
          "network",
          "currency"
        ],
        "allOf": [
          {
            "if": {
              "properties": {
                "network": {
                  "const": "tron"
                }
              },
              "required": [
                "network"
              ]
            },
            "then": {
              "properties": {
                "currency": {
                  "const": "USDT"
                }
              }
            }
          }
        ]
      },
      "SimulateTransfer": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/Money",
            "description": "Positive amount of a virtual incoming transfer; may be less than the requested payment amount."
          },
          "confirmed": {
            "type": "boolean",
            "default": true,
            "description": "false creates an unconfirmed transfer; true applies confirmation immediately."
          },
          "included_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Simulated time of inclusion in the chain, no earlier than payment creation. Defaults to the current time if omitted. Can simulate late inclusion. Whole seconds only; fractional seconds are rejected.",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$"
          }
        },
        "required": [
          "amount"
        ]
      },
      "TopUp": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/Money",
            "description": "Positive virtual top-up amount with no fee."
          }
        },
        "required": [
          "amount"
        ]
      },
      "CreatePayout": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "type": "string",
            "description": "Unique among the company's payouts.",
            "minLength": 1,
            "maxLength": 128
          },
          "amount": {
            "$ref": "#/components/schemas/Money",
            "description": "Positive amount that does not exceed available."
          },
          "destination": {
            "type": "string",
            "minLength": 32,
            "maxLength": 44,
            "description": "Recipient wallet owner address on the selected network. TRON uses T-prefixed base58, Ethereum uses 0x hex, Solana uses an on-curve base58 owner address. Do not supply a Solana token account or mint address. The gateway derives the associated token account for the selected mint."
          },
          "network": {
            "type": "string",
            "enum": [
              "ethereum",
              "tron",
              "solana"
            ],
            "description": "Required explicit blockchain network. The installation host determines mainnet versus testnet."
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC"
            ],
            "description": "Required explicit stablecoin. Must be enabled for this company/network. TRON supports USDT only."
          }
        },
        "required": [
          "external_id",
          "amount",
          "destination",
          "network",
          "currency"
        ],
        "allOf": [
          {
            "if": {
              "properties": {
                "network": {
                  "const": "tron"
                }
              },
              "required": [
                "network"
              ]
            },
            "then": {
              "properties": {
                "currency": {
                  "const": "USDT"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "network": {
                  "const": "tron"
                }
              },
              "required": [
                "network"
              ]
            },
            "then": {
              "properties": {
                "destination": {
                  "pattern": "^T[1-9A-HJ-NP-Za-km-z]{33}$"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "network": {
                  "const": "ethereum"
                }
              },
              "required": [
                "network"
              ]
            },
            "then": {
              "properties": {
                "destination": {
                  "pattern": "^0x[0-9a-fA-F]{40}$"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "network": {
                  "const": "solana"
                }
              },
              "required": [
                "network"
              ]
            },
            "then": {
              "properties": {
                "destination": {
                  "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$"
                }
              }
            }
          }
        ]
      },
      "CompletePayout": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "confirmed",
              "failed"
            ]
          }
        },
        "required": [
          "outcome"
        ]
      },
      "CreateCheckout": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "payment_id": {
            "$ref": "#/components/schemas/PaymentId"
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional compatibility assertion: must equal the Payment return_url override, or the company setting when the payment has no override. To choose a per-payment URL, pass return_url when creating the Payment."
          }
        },
        "required": [
          "payment_id"
        ]
      },
      "EmptyInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {},
        "required": [],
        "description": "Send the JSON object {} with Content-Type: application/json."
      },
      "Payment": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/PaymentId"
          },
          "external_id": {
            "type": "string"
          },
          "amount": {
            "$ref": "#/components/schemas/MoneyOutput"
          },
          "received_amount": {
            "$ref": "#/components/schemas/MoneyOutput",
            "description": "Sum of all confirmed incoming transfers, independent of the informational date or refunds."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "confirming",
              "partially_paid",
              "paid",
              "overpaid"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Optional informational date only. Omitted or null remains null; no default or time-window restrictions. Does not affect payment status, crediting, checkout access or instructions. RFC 3339 whole seconds with timezone."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "origin_operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "unconfirmed_amount": {
            "$ref": "#/components/schemas/MoneyOutput"
          },
          "late_amount": {
            "$ref": "#/components/schemas/MoneyOutput",
            "description": "Legacy historical amount retained for compatibility; no longer updated or used for payment status."
          },
          "version": {
            "type": "integer",
            "minimum": 1
          },
          "deposit_reference": {
            "type": "string",
            "description": "Unique deposit address for a blockchain payment; sandbox:<payment_id> for simulation. Always inspect network and network_mode before sending."
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC"
            ]
          },
          "network": {
            "type": "string",
            "enum": [
              "tron",
              "ethereum",
              "solana"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ]
          },
          "deposit_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Unique address on the indicated network and network_mode. Live payments use mainnet; sandbox uses Shasta, Sepolia or Solana devnet. Null for historical/local simulation. An address alone cannot identify mainnet versus testnet."
          },
          "network_mode": {
            "type": "string",
            "enum": [
              "simulation",
              "shasta",
              "sepolia",
              "devnet",
              "mainnet"
            ],
            "description": "Immutable execution mode of this payment. mainnet means a real payment; shasta/sepolia/devnet use test funds."
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Immutable per-payment browser return URL override; null means checkout falls back to the company setting. This navigation target is not a payment callback."
          },
          "deposit_qr_code": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DepositQrCode"
              },
              {
                "type": "null"
              }
            ],
            "description": "Address QR, null for simulation or absent addresses. Saved webhook snapshots may omit it. Hide payment instructions for completed payments or while a detected/unconfirmed transfer is being checked. The informational date does not restrict payment."
          },
          "chain_progress": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ChainProgress"
              },
              {
                "type": "null"
              }
            ],
            "description": "Advisory Ethereum observation for payment display. It does not change status, version, received_amount or authorize fulfillment. Null for other networks; saved event snapshots may contain null or outdated progress. Fetch the current Payment to refresh this data."
          }
        },
        "required": [
          "id",
          "external_id",
          "amount",
          "received_amount",
          "status",
          "expires_at",
          "created_at",
          "origin_operation_id",
          "unconfirmed_amount",
          "late_amount",
          "version",
          "deposit_reference",
          "currency",
          "network",
          "environment"
        ]
      },
      "Transfer": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/TransferId"
          },
          "payment_id": {
            "$ref": "#/components/schemas/PaymentId"
          },
          "amount": {
            "$ref": "#/components/schemas/MoneyOutput"
          },
          "confirmed": {
            "type": "boolean"
          },
          "included_at": {
            "type": "string",
            "format": "date-time"
          },
          "transaction_id": {
            "type": "string",
            "pattern": "^(?:(?:sim_tx_|0x)?[a-f0-9]{64}|[1-9A-HJ-NP-Za-km-z]{64,88})$",
            "description": "TRON hash, 0x-prefixed Ethereum hash, base58 Solana signature, or a historical synthetic sim_tx_ identifier."
          },
          "origin_operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC"
            ]
          },
          "network": {
            "type": "string",
            "enum": [
              "tron",
              "ethereum",
              "solana"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Installation environment. Never mix live and sandbox accounting or credentials."
          },
          "network_mode": {
            "type": "string",
            "enum": [
              "simulation",
              "shasta",
              "sepolia",
              "devnet",
              "mainnet"
            ],
            "description": "Immutable mode inherited from the parent payment."
          },
          "source_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Verified token sender wallet address when known. Historical or ambiguous deposits may have null; refund creation resolves the original transaction and rejects unknown/ambiguous senders."
          },
          "log_index": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "id",
          "payment_id",
          "amount",
          "confirmed",
          "included_at",
          "transaction_id",
          "origin_operation_id",
          "currency",
          "network",
          "environment",
          "network_mode",
          "source_address",
          "log_index"
        ]
      },
      "Balance": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "balance": {
            "$ref": "#/components/schemas/MoneyOutput",
            "description": "Accounting balance for the selected network and asset before reservations and fees. Separate from the raw token holdings shown in an explorer."
          },
          "available": {
            "$ref": "#/components/schemas/MoneyOutput",
            "description": "balance − reserved − fee_payable."
          },
          "reserved": {
            "$ref": "#/components/schemas/MoneyOutput"
          },
          "fee_payable": {
            "$ref": "#/components/schemas/MoneyOutput"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ]
          },
          "network_mode": {
            "type": "string",
            "enum": [
              "simulation",
              "shasta",
              "sepolia",
              "devnet",
              "mainnet"
            ],
            "description": "Mode of this isolated accounting balance: mainnet, a testnet, or historical/local simulation. Each installation and each network/asset have separate funds."
          },
          "network": {
            "type": "string",
            "enum": [
              "tron",
              "ethereum",
              "solana"
            ]
          }
        },
        "required": [
          "balance",
          "available",
          "reserved",
          "fee_payable",
          "currency",
          "environment",
          "network"
        ]
      },
      "Payout": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/PayoutId"
          },
          "external_id": {
            "type": "string"
          },
          "amount": {
            "$ref": "#/components/schemas/MoneyOutput"
          },
          "destination": {
            "type": "string",
            "description": "Recipient wallet owner address on the recorded network."
          },
          "status": {
            "type": "string",
            "enum": [
              "accepted",
              "confirmed",
              "failed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "origin_operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "version": {
            "type": "integer",
            "minimum": 1
          },
          "transaction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Persisted TRON hash, 0x-prefixed Ethereum hash, or base58 Solana signature when prepared; null before preparation or for simulation. Its presence does not imply broadcast or confirmation."
          },
          "network_mode": {
            "type": "string",
            "enum": [
              "simulation",
              "shasta",
              "sepolia",
              "devnet",
              "mainnet"
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT",
              "USDC"
            ]
          },
          "network": {
            "type": "string",
            "enum": [
              "tron",
              "ethereum",
              "solana"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Installation environment. Never mix live and sandbox accounting or credentials."
          },
          "refund_transfer_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TransferId"
              },
              {
                "type": "null"
              }
            ],
            "description": "Original incoming transfer for a refund payout; null for ordinary payouts."
          },
          "refund_payment_id": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PaymentId"
              },
              {
                "type": "null"
              }
            ],
            "description": "Original incoming payment for a refund payout; null for ordinary payouts."
          }
        },
        "required": [
          "id",
          "external_id",
          "amount",
          "destination",
          "status",
          "created_at",
          "origin_operation_id",
          "version",
          "currency",
          "network",
          "environment",
          "refund_transfer_id",
          "refund_payment_id"
        ]
      },
      "Checkout": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/CheckoutId"
          },
          "payment_id": {
            "$ref": "#/components/schemas/PaymentId"
          },
          "payment_page_url": {
            "type": "string",
            "format": "uri"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Optional informational date only. Omitted or null remains null; no default or time-window restrictions. Does not affect payment status, crediting, checkout access or instructions. RFC 3339 whole seconds with timezone."
          }
        },
        "required": [
          "id",
          "payment_id",
          "payment_page_url",
          "expires_at"
        ]
      },
      "Event": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/EventId"
          },
          "type": {
            "type": "string"
          },
          "resource_id": {
            "type": "string"
          },
          "operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "not_configured",
              "pending",
              "started",
              "delivered",
              "failed"
            ]
          }
        },
        "required": [
          "id",
          "type",
          "resource_id",
          "operation_id",
          "created_at",
          "status"
        ],
        "description": "Event summary. The list does not include the signed raw body or the secret."
      },
      "Acknowledgement": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "$ref": "#/components/schemas/EventId"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued"
            ]
          }
        },
        "required": [
          "id",
          "status"
        ]
      },
      "ErrorDetail": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "code": {
            "type": "string",
            "description": "General error category."
          },
          "error_code": {
            "type": "string",
            "description": "Stable diagnostic code from the registry.",
            "pattern": "^PSP-[A-Z]+-[0-9]{4}$"
          },
          "error_id": {
            "type": "string",
            "pattern": "^err_[a-f0-9]{32}$"
          },
          "message": {
            "type": "string",
            "description": "Safe message without secrets or a stack trace."
          },
          "operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "request_id": {
            "type": "string",
            "pattern": "^req_[a-f0-9]{32}$"
          },
          "origin": {
            "type": "string",
            "description": "Named stage where the error occurred; also recorded in the audit trail."
          }
        },
        "required": [
          "code",
          "error_code",
          "error_id",
          "message",
          "operation_id",
          "request_id",
          "origin"
        ]
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorDetail"
          }
        },
        "required": [
          "error"
        ]
      },
      "PaymentList": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Payment"
        },
        "maxItems": 100,
        "description": "Up to the latest 100 company records, ordered by created_at DESC, id DESC. No pagination or cursor."
      },
      "PayoutList": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Payout"
        },
        "maxItems": 100,
        "description": "Up to the latest 100 company records, ordered by created_at DESC, id DESC. No pagination or cursor."
      },
      "EventList": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Event"
        },
        "maxItems": 200,
        "description": "Up to the latest 200 company records, ordered by created_at DESC, id DESC. No pagination or cursor."
      },
      "PaymentEvent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "event_id": {
            "$ref": "#/components/schemas/EventId"
          },
          "type": {
            "type": "string",
            "enum": [
              "payment.created",
              "payment.updated",
              "payment.confirmed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "data": {
            "$ref": "#/components/schemas/Payment"
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Installation environment. Never mix live and sandbox accounting or credentials. Present on new events; older immutable sandbox event bodies may omit it."
          }
        },
        "required": [
          "event_id",
          "type",
          "created_at",
          "operation_id",
          "data"
        ]
      },
      "TransferEvent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "event_id": {
            "$ref": "#/components/schemas/EventId"
          },
          "type": {
            "type": "string",
            "enum": [
              "transfer.observed",
              "transfer.confirmed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "data": {
            "$ref": "#/components/schemas/Transfer"
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Installation environment. Never mix live and sandbox accounting or credentials. Present on new events; older immutable sandbox event bodies may omit it."
          }
        },
        "required": [
          "event_id",
          "type",
          "created_at",
          "operation_id",
          "data"
        ]
      },
      "PayoutEvent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "event_id": {
            "$ref": "#/components/schemas/EventId"
          },
          "type": {
            "type": "string",
            "enum": [
              "payout.accepted",
              "payout.confirmed",
              "payout.failed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "operation_id": {
            "$ref": "#/components/schemas/OperationId"
          },
          "data": {
            "$ref": "#/components/schemas/Payout"
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Installation environment. Never mix live and sandbox accounting or credentials. Present on new events; older immutable sandbox event bodies may omit it."
          }
        },
        "required": [
          "event_id",
          "type",
          "created_at",
          "operation_id",
          "data"
        ]
      },
      "WebhookEvent": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/PaymentEvent"
          },
          {
            "$ref": "#/components/schemas/TransferEvent"
          },
          {
            "$ref": "#/components/schemas/PayoutEvent"
          }
        ],
        "description": "Stored immutable envelope; event_id is the recipient's deduplication key. created_at and operation_id remain unchanged across all retries."
      },
      "DepositQrCode": {
        "type": "object",
        "additionalProperties": false,
        "description": "Locally generated address-only QR. It does not select a network, token or amount. Display the response network, network_mode, currency, and remaining amount separately.",
        "properties": {
          "payload": {
            "type": "string",
            "pattern": "^(T[1-9A-HJ-NP-Za-km-z]{33}|0x[0-9a-fA-F]{40})$",
            "description": "Exact deposit_address encoded into the image."
          },
          "data_uri": {
            "type": "string",
            "pattern": "^data:image/png;base64,",
            "description": "PNG data URI ready for an image src. Allow data: in your image CSP, or decode and serve it from your own backend. No external QR service is needed."
          }
        },
        "required": [
          "payload",
          "data_uri"
        ]
      },
      "ChainProgress": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status",
          "checked_at",
          "head_block",
          "detected_amount",
          "transfers"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "checking",
              "current",
              "stale"
            ],
            "description": "Freshness of the advisory observation, not a payment settlement status. checking means no completed scan yet; stale retains the last observation when updates are delayed."
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "head_block": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "detected_amount": {
            "$ref": "#/components/schemas/MoneyOutput",
            "description": "Sum of observed successful canonical transfers not yet credited by the finalized scanner. May decrease after a reorganization or disappear after final credit. Never add this to an available balance."
          },
          "transfers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChainProgressTransfer"
            }
          }
        }
      },
      "ChainProgressTransfer": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "transaction_id",
          "log_index",
          "amount",
          "block_number",
          "block_hash",
          "included_at",
          "confirmations",
          "explorer_url"
        ],
        "properties": {
          "transaction_id": {
            "type": "string",
            "pattern": "^0x[0-9a-fA-F]{64}$"
          },
          "log_index": {
            "type": "integer",
            "minimum": 0
          },
          "amount": {
            "$ref": "#/components/schemas/MoneyOutput"
          },
          "block_number": {
            "type": "integer",
            "minimum": 0
          },
          "block_hash": {
            "type": "string"
          },
          "included_at": {
            "type": "string",
            "format": "date-time"
          },
          "confirmations": {
            "type": "integer",
            "minimum": 1,
            "description": "Observed head minus inclusion block plus one. No fixed target is implied; financial credit still requires Ethereum finality."
          },
          "explorer_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "PaymentMethodNetwork": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "network": {
            "type": "string",
            "enum": [
              "ethereum",
              "tron",
              "solana"
            ]
          },
          "standard": {
            "type": "string",
            "enum": [
              "ERC20",
              "TRC20",
              "SPL"
            ]
          },
          "network_mode": {
            "type": "string",
            "enum": [
              "mainnet",
              "shasta",
              "sepolia",
              "devnet"
            ]
          },
          "token_address": {
            "type": "string",
            "description": "Canonical token contract or mint for this installation and asset; never a receiving address. Sandbox USDT on Sepolia/devnet is a dedicated test mint/contract."
          },
          "decimals": {
            "type": "integer",
            "const": 6
          }
        },
        "required": [
          "network",
          "standard",
          "network_mode",
          "token_address",
          "decimals"
        ]
      },
      "PaymentMethods": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "USDT": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentMethodNetwork"
            },
            "description": "Enabled company methods; empty when none are connected."
          },
          "USDC": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentMethodNetwork"
            },
            "description": "Enabled company methods; empty when none are connected."
          }
        },
        "required": [
          "USDT",
          "USDC"
        ]
      },
      "CreateRefund": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/Money",
            "description": "Positive refund amount, no more than original deposit minus pending and confirmed refunds."
          }
        },
        "required": [
          "amount"
        ]
      }
    }
  }
}
