{
  "openapi": "3.1.0",
  "info": {
    "title": "Hodle API",
    "version": "1.0.0",
    "description": "Lightning + PIX in one API. Issue Lightning invoices, run PIX on-ramps and off-ramps, transfer stablecoins, and ship KYC."
  },
  "servers": [
    {
      "url": "https://api.hodle.com.br",
      "description": "Production"
    },
    {
      "url": "https://sandbox-api.hodle.com.br",
      "description": "Sandbox (testnet USDB on Base Sepolia, no real money)"
    }
  ],
  "security": [{ "bearerAuth": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authenticate every request with `Authorization: Bearer YOUR_API_KEY`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": false },
          "error": { "type": "string", "example": "Validation failed" },
          "errorCode": { "type": "string", "nullable": true, "example": "KYC_REQUIRED" },
          "details": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": { "type": "string" },
                "message": { "type": "string" }
              }
            }
          }
        }
      },
      "PixKeyType": {
        "type": "string",
        "enum": ["PHONE", "CPF", "EMAIL", "RANDOM", "CNPJ"]
      },
      "Network": {
        "type": "string",
        "enum": ["polygon", "base", "tron", "liquid", "lightning"]
      },
      "DepositAssetRequest": {
        "type": "object",
        "required": ["value", "asset"],
        "properties": {
          "value": { "type": "integer", "minimum": 1, "example": 5000, "description": "Amount in BRL cents." },
          "address": { "type": "string", "example": "lnbc500u1pj...", "description": "Destination address. BOLT11 invoice or LNURL email for LIGHTNING, EVM address for USDT, USDC, USDCE, and BRLA, Liquid address for DEPIX and LBTC." },
          "asset": { "type": "string", "enum": ["LIGHTNING", "USDT", "USDC", "USDCE", "BRLA", "DEPIX", "LBTC"], "example": "LIGHTNING" },
          "network": { "type": "string", "enum": ["polygon", "arbitrum", "base", "gnosis", "tron", "liquid", "lightning"], "description": "On-chain network. Required when `asset` is `USDT`, `USDC`, `USDCE`, or `BRLA`. Not needed for `LIGHTNING`, `DEPIX`, or `LBTC`." },
          "externalId": { "type": "string", "description": "Idempotency key — generated if not provided." },
          "taxId": { "type": "string", "description": "Beneficiary CPF, digits only. Omit for the account holder." },
          "subAccountId": { "type": "string", "description": "Create the deposit on behalf of this subaccount. Defaults to the main account." }
        }
      },
      "DepositAssetResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "externalId": { "type": "string" },
          "qrCode": { "type": "string", "nullable": true },
          "fee": { "type": "number" },
          "fxRateAtTx": { "type": "number" },
          "walletCharge": { "type": "string", "nullable": true }
        }
      },
      "LightningInvoiceRequest": {
        "type": "object",
        "required": ["value"],
        "properties": {
          "value": { "type": "integer", "minimum": 10, "maximum": 25000, "example": 1000, "description": "BRL cents. Min R$0.10, max R$250.00." },
          "pixKey": { "type": "string", "example": "user@email.com" },
          "pixKeyType": { "$ref": "#/components/schemas/PixKeyType" },
          "qrCode": { "type": "string", "description": "PIX BR Code copy-paste — mutually exclusive with `pixKey`." },
          "refundAddress": { "type": "string", "description": "Lightning address to refund to on failure." }
        }
      },
      "LightningInvoiceResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "invoice": { "type": "string", "example": "lnbc32310n1p5u2g2qsp5..." },
          "valueInSatoshis": { "type": "integer", "example": 276190 },
          "pixKey": { "type": "string", "nullable": true },
          "qrCode": { "type": "string", "nullable": true },
          "valueInBrl": { "type": "string", "example": "1000.00" },
          "fee": { "type": "string", "example": "170.00" }
        }
      },
      "WalletKeysResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "protectedSymmetricKey": { "type": "string" },
              "email": { "type": "string", "format": "email" }
            }
          }
        }
      },
      "WalletGetResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "userId": { "type": "string" },
              "subAccountId": { "type": "string", "nullable": true, "description": "Avenia subaccount the wallet belongs to, when there is one." },
              "syncedAt": { "type": "string", "format": "date-time", "nullable": true },
              "addresses": {
                "type": "object",
                "additionalProperties": { "type": "string" },
                "example": {
                  "polygon": "0x9aB7a1...",
                  "base": "0x9aB7a1...",
                  "tron": "TQn9Y2...",
                  "liquid": "VJL7..."
                }
              },
              "balances": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "asset": { "type": "string" },
                    "network": { "type": "string" },
                    "amount": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      },
      "WalletTransferRequest": {
        "type": "object",
        "required": ["network", "recipientAddress", "amount", "walletPin", "protectedSymmetricKey"],
        "properties": {
          "network": { "type": "string", "enum": ["polygon", "base", "tron"] },
          "recipientAddress": { "type": "string", "example": "0x9aB7a1c2D3e4F5061728394A5B6C7D8E9F0a1B2C" },
          "amount": { "type": "string", "example": "10.00", "description": "USDT amount as a decimal string." },
          "walletPin": { "type": "string", "description": "PIN typed by the end user." },
          "protectedSymmetricKey": { "type": "string", "description": "Returned by `POST /api/wallet/keys`." }
        }
      },
      "WalletTransferResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "txHash": { "type": "string" },
              "network": { "type": "string" },
              "recipientAddress": { "type": "string" },
              "asset": { "type": "string", "example": "USDT" },
              "amount": { "type": "string" }
            }
          }
        }
      },
      "WalletPayoutRequest": {
        "type": "object",
        "required": ["value", "network"],
        "properties": {
          "value": { "type": "integer", "minimum": 10, "example": 5000, "description": "BRL cents. Minimum 10 (R$ 0.10). There is no fixed maximum in the schema — the ceiling is your per-account per-transaction limit (`perTxLimitBrl`, default R$ 3000). Exceeding it returns 403 `PER_TX_LIMIT_EXCEEDED`." },
          "network": { "type": "string", "enum": ["polygon", "base", "tron"], "description": "Tron additionally requires the `TRON_PAYOUT` feature flag." },
          "asset": { "type": "string", "enum": ["USDT", "USDC", "BRLA"], "description": "Funding stablecoin. `polygon`: `USDT` (default) or `BRLA`. `base`: `USDC` (default) or `BRLA`. `tron`: `USDT` only." },
          "pixKey": { "type": "string" },
          "pixKeyType": { "$ref": "#/components/schemas/PixKeyType" },
          "qrCode": { "type": "string", "description": "PIX BR Code — mutually exclusive with `pixKey`." },
          "walletPin": { "type": "string" },
          "protectedSymmetricKey": { "type": "string" },
          "taxId": { "type": "string", "description": "CPF of the beneficiary receiving the PIX, digits only. Omit for the account holder. A different value is a third-party operation and returns 403 unless third-party operations are enabled for the account." },
          "subAccountId": { "type": "string", "description": "Pay out from this subaccount's wallet. `walletPin` and `protectedSymmetricKey` must be the subaccount's. Defaults to the main account." }
        }
      },
      "WalletPayoutResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "transactionId": { "type": "string", "example": "65f1a8..." },
          "status": { "type": "string", "example": "PROCESSING" },
          "stableAmount": { "type": "string", "example": "9.31" },
          "valueInBrl": { "type": "string", "example": "50.00" },
          "fee": { "type": "string", "example": "2.75" },
          "network": { "type": "string", "example": "polygon" },
          "jobId": { "type": "string" }
        }
      },
      "WalletPayoutStatusResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "transactionId": { "type": "string" },
              "status": { "type": "string", "enum": ["PENDING", "COMPLETED", "FAILED", "REFUNDED"] },
              "network": { "type": "string" },
              "txHash": { "type": "string", "nullable": true },
              "endToEndId": { "type": "string", "nullable": true },
              "valueInBrl": { "type": "string" },
              "fee": { "type": "string" },
              "pixKey": { "type": "string", "nullable": true },
              "qrCode": { "type": "string", "nullable": true },
              "provider": { "type": "string" },
              "correlationID": { "type": "string", "nullable": true },
              "failureReason": { "type": "string", "nullable": true },
              "createdAt": { "type": "string", "format": "date-time" },
              "updatedAt": { "type": "string", "format": "date-time" }
            }
          }
        }
      },
      "AccountStatementResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "balances": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "asset": { "type": "string" },
                    "network": { "type": "string", "nullable": true },
                    "amount": { "type": "string" }
                  }
                }
              },
              "operations": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string" },
                    "type": { "type": "string", "enum": ["DEPOSIT_ASSET", "PIX_OUT", "LIGHTNING_OUT", "WALLET_TRANSFER", "FEE"] },
                    "status": { "type": "string" },
                    "asset": { "type": "string" },
                    "network": { "type": "string" },
                    "valueInBrl": { "type": "string" },
                    "stableAmount": { "type": "string", "nullable": true },
                    "fee": { "type": "string" },
                    "txHash": { "type": "string", "nullable": true },
                    "endToEndId": { "type": "string", "nullable": true },
                    "createdAt": { "type": "string", "format": "date-time" },
                    "updatedAt": { "type": "string", "format": "date-time" }
                  }
                }
              },
              "page": { "type": "integer" },
              "pageSize": { "type": "integer" },
              "total": { "type": "integer" }
            }
          }
        }
      },
      "SubAccountCreateRequest": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "minLength": 1, "maxLength": 64, "example": "Maria Silva" },
          "email": { "type": "string", "format": "email", "description": "Optional. Identifies the subaccount owner on KYC/KYB and in your statements. Omitting it no longer blocks later calls: the subaccount is always resolvable by `subAccountId`." },
          "accountType": { "type": "string", "enum": ["INDIVIDUAL", "COMPANY"], "default": "INDIVIDUAL" }
        }
      },
      "SubAccountListResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "subAccounts": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "subAccountId": { "type": "string", "example": "883f2eec-0cea-44aa-9554-67055066bbc7" },
                    "email": { "type": "string", "example": "maria@example.com" },
                    "owner": { "type": "string", "enum": ["SELF", "CUSTOMER"] },
                    "createdAt": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          }
        }
      },
      "SubAccountCreateResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "subAccountId": { "type": "string", "example": "883f2eec-0cea-44aa-9554-67055066bbc7" }
            }
          }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "required": ["inputCurrency", "inputPaymentMethod", "outputCurrency", "outputPaymentMethod"],
        "description": "Pass exactly one of `inputAmount` or `outputAmount`.",
        "properties": {
          "inputCurrency": { "type": "string", "enum": ["BRL", "USDT", "USDC", "USDB", "LBTC", "BTC"] },
          "inputPaymentMethod": { "type": "string", "enum": ["PIX", "POLYGON", "BASE", "TRON", "LIQUID", "LIGHTNING", "SPARK"] },
          "outputCurrency": { "type": "string", "enum": ["BRL", "USDT", "USDC", "USDB", "LBTC", "BTC"] },
          "outputPaymentMethod": { "type": "string", "enum": ["PIX", "POLYGON", "BASE", "TRON", "LIQUID", "LIGHTNING", "SPARK"] },
          "inputAmount": { "type": "string", "example": "100.00", "description": "Decimal string." },
          "outputAmount": { "type": "string", "description": "Decimal string. Use for exact-out quotes." },
          "outputBrCode": { "type": "string", "description": "PIX BR Code, when the output goes to a known PIX recipient. Routes and prices this quote only — it grants no third-party permission." }
        }
      },
      "QuoteResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "quoteToken": { "type": "string", "description": "Provider-side reference, returned for traceability. No Hodle endpoint accepts it as input — a quote is indicative pricing, not a rate lock." },
              "pairName": { "type": "string", "example": "BRL-USDC" },
              "basePrice": { "type": "string" },
              "inputCurrency": { "type": "string" },
              "outputCurrency": { "type": "string" },
              "inputAmount": { "type": "string" },
              "outputAmount": { "type": "string" },
              "appliedFees": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "type": { "type": "string" },
                    "amount": { "type": "string" },
                    "currency": { "type": "string" }
                  }
                }
              },
              "expiresAt": { "type": "string", "format": "date-time", "description": "Two minutes after creation. Marks the price as stale, reserves nothing." }
            }
          }
        }
      },
      "KycCreateRequest": {
        "type": "object",
        "description": "Hosted flow. Omit `subAccountId` to run KYC for the API-key user itself.",
        "properties": {
          "subAccountId": { "type": "string", "description": "Subaccount the KYC belongs to." },
          "level": { "type": "string", "enum": ["LIGHT", "FULL"], "default": "FULL" },
          "redirectUrl": { "type": "string", "format": "uri", "description": "Where the customer lands once the hosted flow finishes." }
        }
      },
      "KycSubmitFormRequest": {
        "type": "object",
        "description": "Form flow, for integrators that collect the data and upload the documents themselves via `POST /api/kyc/document`. If the subaccount was created without an `email`, the one sent here becomes its email on file.",
        "required": ["subAccountId", "fullName", "dateOfBirth", "countryOfTaxId", "taxIdNumber", "email", "country", "state", "city", "zipCode", "streetAddress", "uploadedSelfieId", "uploadedDocumentId"],
        "properties": {
          "subAccountId": { "type": "string" },
          "fullName": { "type": "string" },
          "dateOfBirth": { "type": "string", "example": "1990-01-31", "description": "YYYY-MM-DD." },
          "countryOfTaxId": { "type": "string", "example": "BRA", "description": "ISO 3166-1 alpha-3." },
          "taxIdNumber": { "type": "string", "description": "Digits only." },
          "email": { "type": "string", "format": "email" },
          "phone": { "type": "string" },
          "country": { "type": "string", "example": "BRA" },
          "state": { "type": "string" },
          "city": { "type": "string" },
          "zipCode": { "type": "string" },
          "streetAddress": { "type": "string" },
          "uploadedSelfieId": { "type": "string", "description": "Document id returned by POST /api/kyc/document." },
          "uploadedDocumentId": { "type": "string", "description": "Document id returned by POST /api/kyc/document." }
        }
      },
      "KycCreateResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "attemptId": { "type": "string" },
              "status": { "type": "string", "example": "PENDING" },
              "url": { "type": "string", "format": "uri" },
              "expiresAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Null when the provider does not publish an expiry for the hosted link." }
            }
          }
        }
      },
      "KycImportTokenRequest": {
        "type": "object",
        "required": ["importToken"],
        "properties": {
          "subAccountId": { "type": "string", "description": "Subaccount the verification is imported into. Omit to import into the API-key account itself. Must be an INDIVIDUAL subaccount." },
          "importToken": { "type": "string", "description": "Single-use Sumsub share token, minted on your own Sumsub account for a GREEN applicant with Hodle's provider as `forClientId`. Never stored by Hodle." }
        }
      },
      "KycImportTokenResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "attemptId": { "type": "string" },
              "status": { "type": "string", "example": "PENDING" },
              "source": { "type": "string", "example": "SUMSUB_IMPORT" },
              "createdAt": { "type": "string", "format": "date-time" }
            }
          }
        }
      },
      "KycGetResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "status": { "type": "string", "enum": ["NONE", "PENDING", "REVIEW", "APPROVED", "REJECTED", "EXPIRED", "FAILED"] },
              "level": { "type": "string", "enum": ["LIGHT", "FULL"] },
              "lastAttemptId": { "type": "string", "nullable": true },
              "approvedAt": { "type": "string", "format": "date-time", "nullable": true }
            }
          }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "DEPOSIT_ASSET_SUCCESS",
              "PAYOUT_SUCCESSFUL",
              "PAYOUT_FAILED",
              "KYC_APPROVED",
              "KYC_REJECTED",
              "KYC_EXPIRED",
              "KYC_FAILED",
              "PAYOUT_REFUNDED",
              "DISPUTE_CREATED",
              "DISPUTE_ACCEPTED",
              "DISPUTE_REJECTED",
              "DISPUTE_CANCELED"
            ]
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Event-specific payload. See the Webhooks guide for the shape per event."
          }
        }
      }
    }
  },
  "paths": {
    "/api/subaccount": {
      "post": {
        "operationId": "subAccountCreate",
        "summary": "Create a subaccount",
        "description": "Create an end-user subaccount under your API key. Subaccounts are permanent — deletion is not supported. Pass `email` so later KYC/KYB and operation calls can resolve it.",
        "tags": ["Subaccounts"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubAccountCreateRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subaccount created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubAccountCreateResponse" }
              }
            }
          },
          "400": {
            "description": "Validation failed or the request was rejected upstream.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "401": {
            "description": "Missing key, or key does not match the base URL environment.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "subAccountList",
        "summary": "List your subaccounts",
        "tags": ["Subaccounts"],
        "responses": {
          "200": {
            "description": "Subaccounts scoped to your API key: your own Avenia subaccount (`owner: SELF`) plus the ones you created for customers (`owner: CUSTOMER`).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubAccountListResponse" }
              }
            }
          },
          "401": {
            "description": "Missing key, or key does not match the base URL environment.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/subaccount/{subAccountId}": {
      "get": {
        "operationId": "subAccountGet",
        "summary": "Read one subaccount",
        "tags": ["Subaccounts"],
        "parameters": [
          { "name": "subAccountId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Subaccount detail." },
          "404": {
            "description": "Not found, or not scoped to your API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/quote": {
      "post": {
        "operationId": "quote",
        "summary": "Price a BRL ↔ asset conversion",
        "description": "Indicative pricing and fee breakdown for a pair. The returned `quoteToken` is informational — no endpoint consumes it, so a quote does not lock a rate. Price the operation immediately before executing it.",
        "tags": ["Pricing"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/QuoteRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/QuoteResponse" }
              }
            }
          },
          "400": {
            "description": "Validation failed, amount below minimum, or pair not routable.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/kyc": {
      "get": {
        "operationId": "kycGet",
        "summary": "Get current KYC state",
        "description": "State of the API-key user, or of one subaccount when `subAccountId` is given. Answers `200` with `status: NONE` when nothing was ever submitted.",
        "tags": ["KYC"],
        "parameters": [
          { "name": "subAccountId", "in": "query", "schema": { "type": "string" }, "description": "Read the state of this subaccount instead of the API-key user." }
        ],
        "responses": {
          "200": {
            "description": "KYC state.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/KycGetResponse" }
              }
            }
          },
          "404": { "description": "Subaccount not found for this platform." }
        }
      },
      "post": {
        "operationId": "kycCreate",
        "summary": "Start a KYC attempt",
        "description": "Two payloads are accepted on this route. Send `{ level, redirectUrl }` (optionally `subAccountId`) to open the **hosted flow** — the response carries the `url` the customer must visit. Send the **form payload** (`KycSubmitFormRequest`) when you collect and upload the documents yourself; it answers `202` with the attempt id instead of a url.",
        "tags": ["KYC"],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  { "$ref": "#/components/schemas/KycCreateRequest" },
                  { "$ref": "#/components/schemas/KycSubmitFormRequest" }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Hosted KYC attempt created. Send the customer to `data.url`.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/KycCreateResponse" }
              }
            }
          },
          "202": { "description": "Form submission accepted. Poll `GET /api/kyc/{attemptId}` for the result." },
          "400": { "description": "Validation error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized." }
        }
      }
    },
    "/api/kyc/import-token": {
      "post": {
        "operationId": "kycImportToken",
        "summary": "Import an existing Sumsub verification",
        "description": "Reuse a KYC you already ran on your own Sumsub account instead of verifying the user again. Post a single-use share token; Hodle forwards it to the provider, which reads the applicant back and opens a KYC attempt from it. A `202` means the token was accepted for processing, not that the user is approved \u2014 the applicant is validated asynchronously, so poll `GET /api/kyc/{attemptId}` or wait for the `kyc.completed` webhook. One token per user, and each token can only be imported once.",
        "tags": ["KYC"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/KycImportTokenRequest" }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Token accepted for processing. Poll `GET /api/kyc/{attemptId}` for the result.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/KycImportTokenResponse" }
              }
            }
          },
          "400": { "description": "Missing token, or a token the provider refused (`errorCode`: `INVALID_IMPORT_TOKEN`, `IMPORT_FAILED`).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Unauthorized." },
          "404": { "description": "Subaccount not found for this platform." },
          "409": { "description": "Token already consumed, or an import for this subaccount is still running (`errorCode`: `IMPORT_TOKEN_ALREADY_USED`, `IMPORT_IN_PROGRESS`).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "502": { "description": "Sumsub import is not enabled for this account (`errorCode`: `IMPORT_NOT_ENABLED`).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/account/statement": {
      "get": {
        "operationId": "accountStatement",
        "summary": "Account statement",
        "description": "Returns balances per asset and a paginated list of operations. Requires full KYC.",
        "tags": ["Account"],
        "parameters": [
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date-time" }, "description": "ISO date/datetime. Defaults to 30 days ago." },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date-time" }, "description": "ISO date/datetime. Defaults to now. Window must be at most 90 days." },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "maximum": 200, "default": 50 }, "description": "Max operations per page." },
          { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Opaque cursor from a previous response's nextCursor." },
          { "name": "type", "in": "query", "schema": { "type": "array", "items": { "type": "string", "enum": ["deposit", "withdraw", "payout", "transfer"] } }, "description": "Filter by operation type. Repeat the param to pass multiple." }
        ],
        "responses": {
          "200": {
            "description": "Statement.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AccountStatementResponse" }
              }
            }
          },
          "403": { "description": "Full KYC required.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/wallet": {
      "get": {
        "operationId": "walletGet",
        "summary": "Read a wallet",
        "description": "Returns the addresses per network and current balances of the API-key user, or of one subaccount when `subAccountId` is given. `POST /api/wallet/get` serves the same payload with the fields in the body.",
        "tags": ["Wallet"],
        "parameters": [
          { "name": "subAccountId", "in": "query", "schema": { "type": "string" }, "description": "Read this subaccount's wallet instead of the API-key user's." }
        ],
        "responses": {
          "200": {
            "description": "Wallet.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WalletGetResponse" }
              }
            }
          },
          "404": { "description": "Wallet not provisioned." }
        }
      }
    },
    "/api/wallet/keys": {
      "post": {
        "operationId": "walletKeys",
        "summary": "Fetch protectedSymmetricKey",
        "description": "Returns the user's `protectedSymmetricKey` and `email`. Cache once per user. Required by `/api/wallet/payout` and `/api/wallet/transfer`.",
        "tags": ["Wallet"],
        "responses": {
          "200": {
            "description": "Keys.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WalletKeysResponse" }
              }
            }
          },
          "403": { "description": "Feature flag disabled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited (1/min)." }
        }
      }
    },
    "/api/wallet/transfer": {
      "post": {
        "operationId": "walletTransfer",
        "summary": "Transfer USDT to any address",
        "description": "Move USDT from the API-key user's wallet to any address on the chosen network. Gas sponsored on EVM.",
        "tags": ["Wallet"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WalletTransferRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transfer mined.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WalletTransferResponse" }
              }
            }
          },
          "400": { "description": "Validation / insufficient balance / wrong PIN.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/wallet/payout": {
      "post": {
        "operationId": "walletPayout",
        "summary": "Trigger a PIX payout from stable balance",
        "description": "Trigger a PIX payout funded by USDT (Polygon/Tron) or USDC (Base). Returns 202 with a `transactionId`. Poll `/api/wallet/payout/{transactionId}` until terminal state.",
        "tags": ["Wallet"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WalletPayoutRequest" },
              "examples": {
                "withPixKey": {
                  "summary": "With PIX key (Polygon USDT)",
                  "value": {
                    "value": 5000,
                    "network": "polygon",
                    "pixKey": "user@example.com",
                    "pixKeyType": "EMAIL",
                    "walletPin": "1234",
                    "protectedSymmetricKey": "AoofiKHy..."
                  }
                },
                "withQrCode": {
                  "summary": "With PIX BR Code (Base USDC)",
                  "value": {
                    "value": 5000,
                    "network": "base",
                    "qrCode": "00020126580014br.gov.bcb.pix...",
                    "walletPin": "1234",
                    "protectedSymmetricKey": "AoofiKHy..."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Payout queued.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WalletPayoutResponse" }
              }
            }
          },
          "400": { "description": "Validation / insufficient balance / wrong PIN.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Full KYC or feature flag missing.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited (30s window)." },
          "503": { "description": "PIX globally disabled." }
        }
      }
    },
    "/api/wallet/payout/{transactionId}": {
      "get": {
        "operationId": "walletPayoutStatus",
        "summary": "Get payout status",
        "tags": ["Wallet"],
        "parameters": [
          { "name": "transactionId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Status.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WalletPayoutStatusResponse" }
              }
            }
          }
        }
      }
    },
    "/api/deposit/asset": {
      "post": {
        "operationId": "depositAsset",
        "summary": "Create a deposit (PIX → crypto)",
        "description": "Convert BRL to Lightning, USDT, USDC, or USDCE and deliver to an address. Requires full KYC.",
        "tags": ["Deposits"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/DepositAssetRequest" },
              "examples": {
                "lightning": {
                  "summary": "Lightning deposit",
                  "value": { "value": 5000, "address": "lnbc500u1pj...", "asset": "LIGHTNING", "externalId": "order-001" }
                },
                "usdt": {
                  "summary": "USDT (Polygon) deposit",
                  "value": { "value": 25000, "asset": "USDT", "network": "polygon", "address": "0x9aB7a1..." }
                },
                "usdc": {
                  "summary": "USDC (Base) deposit",
                  "value": { "value": 25000, "asset": "USDC", "network": "base", "address": "0x9aB7a1..." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deposit created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DepositAssetResponse" }
              }
            }
          },
          "400": { "description": "Validation error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Full KYC required.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Duplicate `externalId`." }
        }
      }
    },
    "/api/lightning/invoice": {
      "post": {
        "operationId": "createLightningInvoice",
        "summary": "Create a Lightning invoice (Lightning → PIX)",
        "description": "Generate a Lightning invoice that, once paid, triggers an automatic PIX payout.",
        "tags": ["Lightning"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LightningInvoiceRequest" },
              "examples": {
                "withPixKey": {
                  "summary": "With PIX key",
                  "value": { "value": 1000, "pixKey": "user@email.com", "pixKeyType": "EMAIL" }
                },
                "withQrCode": {
                  "summary": "With PIX BR Code",
                  "value": { "value": 1000, "qrCode": "00020126580014br.gov.bcb.pix..." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LightningInvoiceResponse" }
              }
            }
          },
          "400": { "description": "Validation error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/webhook/{webhookId}": {
      "post": {
        "operationId": "webhookDelivery",
        "summary": "Webhook delivery (your server receives this)",
        "description": "Hodle POSTs to your endpoint with `event` + `data`. Verify the `X-Hodle-Signature` header (HMAC-SHA256 of `${timestamp}.${rawBody}`).",
        "tags": ["Webhooks"],
        "parameters": [
          { "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "X-Hodle-Signature", "in": "header", "required": true, "schema": { "type": "string" }, "description": "HMAC-SHA256 of `${timestamp}.${payload}` using your webhook secret." },
          { "name": "X-Hodle-Timestamp", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WebhookPayload" }
            }
          }
        },
        "responses": {
          "200": { "description": "Acknowledged. 2xx is required to mark delivery as successful." }
        }
      }
    }
  },
  "tags": [
    { "name": "Subaccounts", "description": "Create and read end-user subaccounts." },
    { "name": "KYC", "description": "Submit and inspect KYC for end-users." },
    { "name": "Account", "description": "Statement and balances." },
    { "name": "Pricing", "description": "Indicative quotes for BRL ↔ asset pairs." },
    { "name": "Wallet", "description": "Read wallets, fetch keys, transfer USDT, run PIX payouts." },
    { "name": "Deposits", "description": "PIX → crypto on-ramp." },
    { "name": "Lightning", "description": "Lightning ↔ PIX off-ramp." },
    { "name": "Webhooks", "description": "Realtime event delivery." }
  ]
}
