openapi: 3.1.0
info:
  title: Throne API
  version: 1.0.0
  summary: API REST do gateway Throne · PIX in/out + cripto out.
  description: |
  ## API REST do Throne
  
  Use esta API pra integrar o **Throne Gateway** no seu checkout, e-commerce
  ou plataforma de assinatura. Suporta:
  
  - **Criar cobranças PIX** (QR Code copia-e-cola)
  - **Solicitar saques** PIX e cripto (com 2FA TOTP)
  - **Consultar saldo** disponível em tempo real
  - **Receber webhooks** dos eventos `transaction.paid`, `withdrawal.completed`, `withdrawal.failed`
  
  ### Autenticação
  
  Todas as requisições usam `Authorization: Bearer pk_live_xxx:sk_live_xxx`.
  
  - **Public key** (`pk_live_*` ou `pk_test_*`) é o ID público da sua API key.
  - **Secret key** (`sk_live_*` ou `sk_test_*`) é o segredo · **NUNCA exponha em código cliente**.
  
  Gere suas keys em [app.throne.com.br/painel/api-keys](https://app.throne.com.br/painel/api-keys).
  O secret aparece UMA VEZ na criação; depois é só hash bcrypt no servidor.
  
  ### Idempotência
  
  Toda operação mutante (POST/PUT/PATCH/DELETE) **exige** o header
  `Idempotency-Key`. Recomendamos um UUID v4 por operação. Se o cliente
  mandar a mesma key duas vezes em até 24h, retornamos a **mesma resposta**
  sem efeito duplicado · útil pra retry seguro.
  
  ### Rate limits
  
  | Recurso | Limite |
  |---|---|
  | API geral | 10 req/seg/key |
  | Criação de saques | 1/min/seller |
  | Webhooks ingress | 50 req/seg/IP |
  
  ### Money em centavos
  
  Todos os valores monetários são **inteiros em centavos** (BIGINT). Ex:
  R$ 10,50 = `1050`. Nunca floats · evita erros de arredondamento.
  contact:
    name: Throne · Suporte Developers
    email: 'developers@throne.com.br'
    url: 'https://throne.com.br/developers'
  license:
    name: Proprietary
servers:
- url: 'https://app.thronecorporate.com/api/v1'
    description: API pública
security:
- bearerAuth: []
tags:
- name: Saúde
    description: Endpoint de saúde sem auth.
- name: Transações
    description: Cobranças PIX (QR Code).
- name: Saques
    description: PIX out e cripto out (exigem 2FA).
- name: Saldo
    description: Consulta de saldo da wallet.
paths:
  /health:
    get:
      tags:
      - Saúde
      summary: Health check (sem auth)
      security: []
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
  /balance:
    get:
      tags:
      - Saldo
      summary: Saldo atual da wallet
      description: Retorna saldo disponível, reservado (em saques) e pendente.
      responses:
        200:
          description: Saldo retornado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Balance'
              example:
                balance_cents: 543210
                pending_balance_cents: 0
                reserved_cents: 50000
                available_cents: 543210
                currency: BRL
        401:
          $ref: '#/components/responses/Unauthorized'
        429:
          $ref: '#/components/responses/RateLimited'
  /transactions:
    post:
      tags:
      - Transações
      summary: Criar cobrança PIX
      description: 'Gera QR Code copia-e-cola que o cliente final paga. Status inicial = `pending`. Quando o pagamento for confirmado pela adquirente, disparamos webhook `transaction.paid` e creditamos o saldo do seller.'
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTransactionRequest'
            example:
              amount_cents: 10000
              payment_method: pix
              idempotency_key: tx-pedido-2026-001
              expires_at: '2026-05-06T18:00:00Z'
              customer:
                name: João Cliente
                document: 12345678901
                email: 'joao@example.com'
                phone: +5511999999999
              metadata:
                order_id: PEDIDO-42
                sku: CAMISA-AZUL-M
      responses:
        201:
          description: Transação criada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Transaction'
        400:
          $ref: '#/components/responses/BadRequest'
        401:
          $ref: '#/components/responses/Unauthorized'
        403:
          $ref: '#/components/responses/Forbidden'
        422:
          $ref: '#/components/responses/ValidationError'
        429:
          $ref: '#/components/responses/RateLimited'
        502:
          $ref: '#/components/responses/AcquirerError'
  /transactions/{id}:
    get:
      tags:
      - Transações
      summary: Consultar transação
      parameters:
      - $ref: '#/components/parameters/PublicId'
      responses:
        200:
          description: Transação encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Transaction'
        404:
          $ref: '#/components/responses/NotFound'
  /transactions/list:
    get:
      tags:
      - Transações
      summary: Listar transações (paginado)
      description: Lista as transações do seller. Aceita filtros por status, período e payment_method. Paginação cursor-based.
      parameters:
      - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
            - pending
            - paid
            - failed
            - refunded
            - chargeback
            - expired
          description: Filtra por status.
      - name: from
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Data inicial (YYYY-MM-DD).
      - name: until
          in: query
          required: false
          schema:
            type: string
            format: date
          description: Data final (YYYY-MM-DD).
      - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
      - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: 'Cursor da página seguinte (opaco, vem em `next_cursor`).'
      responses:
        200:
          description: Lista paginada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionList'
        401:
          $ref: '#/components/responses/Unauthorized'
        429:
          $ref: '#/components/responses/RateLimited'
  /transactions/{id}/refund:
    post:
      tags:
      - Transações
      summary: Estornar transação paga
      description: 'Cria entry tipo `reverse` no ledger. Estorno parcial OK (passe `amount_cents`). Se omitir, estorna o líquido total.'
      parameters:
      - $ref: '#/components/parameters/PublicId'
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - reason
              properties:
                amount_cents:
                  type: integer
                  description: Valor a estornar em centavos. Omitir = estorna o líquido total.
                  example: 5000
                reason:
                  type: string
                  minLength: 10
                  description: Motivo (mínimo 10 caracteres) · fica no audit log.
                  example: Cliente solicitou cancelamento dentro do prazo de arrependimento
      responses:
        200:
          description: Estorno aceito
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Transaction'
        404:
          $ref: '#/components/responses/NotFound'
        422:
          $ref: '#/components/responses/ValidationError'
  /withdrawals:
    post:
      tags:
      - Saques
      summary: Solicitar saque PIX ou cripto
      description: 'Exige **2FA TOTP** (`totp_code`) e **2FA habilitado** no seller. Lock pessimista impede over-draft mesmo em chamadas concorrentes. Após criar, status = `requested`. Webhook `withdrawal.completed` ou `withdrawal.failed` chega quando o banco confirma.'
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWithdrawalRequest'
            examples:
              pix:
                summary: Saque PIX
                value:
                  amount_cents: 50000
                  totp_code: 123456
                  idempotency_key: wdr-2026-05-06-001
                  destination:
                    key: 'a@b.com'
                    key_type: email
              crypto:
                summary: Saque cripto USDT TRC20
                value:
                  amount_cents: 100000
                  totp_code: 654321
                  idempotency_key: wdr-crypto-001
                  destination:
                    address: TXXxxxxxxxxxxxxxxxxxxxxxxxxxx
                    network: USDT_TRC20
      responses:
        201:
          description: Saque criado (status=requested)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Withdrawal'
        401:
          description: 2FA inválido
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Seller não aprovado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        422:
          description: Saldo insuficiente / validação
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        429:
          description: Limite diário ou rate limit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /withdrawals/{id}:
    get:
      tags:
      - Saques
      summary: Consultar saque
      parameters:
      - $ref: '#/components/parameters/PublicId'
      responses:
        200:
          description: Saque encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Withdrawal'
        404:
          $ref: '#/components/responses/NotFound'
  /withdrawals/list:
    get:
      tags:
      - Saques
      summary: Listar saques (paginado)
      parameters:
      - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
            - requested
            - approved
            - processing
            - completed
            - failed
            - reversed
      - name: type
          in: query
          required: false
          schema:
            type: string
            enum:
            - pix
            - crypto
      - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
      - name: cursor
          in: query
          required: false
          schema:
            type: string
      responses:
        200:
          description: Lista paginada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalList'
        401:
          $ref: '#/components/responses/Unauthorized'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: 'pk_live_xxx:sk_live_xxx'
      description: 'Bearer com `<public_key>:<secret_key>` (separadas por dois pontos).'
  schemas:
    Health:
      type: object
      properties:
        status:
          type: string
          enum:
          - ok
        env:
          type: string
          example: production
        time:
          type: string
          format: date-time
    Balance:
      type: object
      required:
      - balance_cents
      - currency
      properties:
        balance_cents:
          type: integer
          description: Disponível AGORA pra saque.
          example: 543210
        pending_balance_cents:
          type: integer
          description: Aguardando liquidação ou revisão. Vira disponível automaticamente no prazo definido pela equipe Throne.
          example: 0
        reserved_cents:
          type: integer
          description: Reservado em saques em andamento.
          example: 50000
        available_cents:
          type: integer
          description: Alias de balance_cents (pra clareza).
        currency:
          type: string
          enum:
          - BRL
    Customer:
      type: object
      description: Dados do cliente final pagador. Cifrados em rest no Throne.
      properties:
        name:
          type: string
          maxLength: 255
          example: João Cliente
        document:
          type: string
          description: CPF (11 dígitos) ou CNPJ (14)
          example: 12345678901
        email:
          type: string
          format: email
          example: 'joao@example.com'
        phone:
          type: string
          description: E.164 com +55
          example: +5511999999999
    CreateTransactionRequest:
      type: object
      required:
      - amount_cents
      - payment_method
      properties:
        amount_cents:
          type: integer
          minimum: 1
          maximum: 5000000
          description: Valor em centavos.
        payment_method:
          type: string
          enum:
          - pix
        idempotency_key:
          type: string
          minLength: 8
          maxLength: 128
          description: Pode vir no body OU no header Idempotency-Key.
        expires_at:
          type: string
          format: date-time
          description: 'Quando o QR expira. Default: +1h.'
        customer:
          $ref: '#/components/schemas/Customer'
        metadata:
          type: object
          additionalProperties: true
          description: Qualquer JSON livre (até 4KB) que volta nos webhooks.
    PaymentData:
      type: object
      description: Dados pra exibir QR Code no checkout.
      properties:
        qr_code_payload:
          type: string
          description: BR Code copia-e-cola.
        qr_code_image_base64:
          type: string
          nullable: true
          description: 'PNG do QR em base64 (sem prefixo data:).'
        expires_at:
          type: string
          format: date-time
    Transaction:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
          - pending
          - paid
          - failed
          - refunded
          - chargeback
          - expired
        payment_method:
          type: string
          enum:
          - pix
        amount_cents:
          type: integer
        fee_cents:
          type: integer
        net_amount_cents:
          type: integer
        paid_at:
          type: string
          format: date-time
          nullable: true
        expires_at:
          type: string
          format: date-time
          nullable: true
        payment_data:
          $ref: '#/components/schemas/PaymentData'
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
    PixDestination:
      type: object
      required:
      - key
      - key_type
      properties:
        key:
          type: string
          description: Chave PIX do destinatário.
        key_type:
          type: string
          enum:
          - cpf
          - cnpj
          - email
          - phone
          - evp
    CryptoDestination:
      type: object
      required:
      - address
      - network
      properties:
        address:
          type: string
        network:
          type: string
          enum:
          - BTC
          - ETH
          - USDT_TRC20
          - USDT_ERC20
    CreateWithdrawalRequest:
      type: object
      required:
      - amount_cents
      - totp_code
      - destination
      properties:
        amount_cents:
          type: integer
          minimum: 1000
          description: Mínimo R$ 10,00.
        totp_code:
          type: string
          description: 6 dígitos do app autenticador do seller.
        idempotency_key:
          type: string
          minLength: 8
          maxLength: 128
        destination:
          oneOf:
          - $ref: '#/components/schemas/PixDestination'
          - $ref: '#/components/schemas/CryptoDestination'
    Withdrawal:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
          - pix
          - crypto
        status:
          type: string
          enum:
          - requested
          - approved
          - processing
          - completed
          - failed
          - reversed
        amount_cents:
          type: integer
        fee_cents:
          type: integer
        net_amount_cents:
          type: integer
        requested_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
        failure_reason:
          type: string
          nullable: true
    TransactionList:
      type: object
      required:
      - data
      - has_more
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Transaction'
        has_more:
          type: boolean
          description: Tem mais páginas após esta.
        next_cursor:
          type: string
          nullable: true
          description: Cursor para a próxima página (opaco).
    WithdrawalList:
      type: object
      required:
      - data
      - has_more
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Withdrawal'
        has_more:
          type: boolean
        next_cursor:
          type: string
          nullable: true
    ErrorResponse:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: Código machine-readable.
          example: insufficient_balance
        message:
          type: string
          description: Mensagem humana em pt-BR.
          example: 'Saldo insuficiente: disponível 30000 centavos, solicitado 50200 centavos.'
        detail:
          type: string
          description: Detalhe adicional.
          nullable: true
    ValidationErrorResponse:
      type: object
      properties:
        message:
          type: string
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          example:
            amount_cents:
            - The amount cents must be at least 1.
    WebhookTransactionPaid:
      type: object
      properties:
        type:
          type: string
          enum:
          - transaction.paid
        created_at:
          type: string
          format: date-time
        data:
          type: object
          properties:
            transaction_id:
              type: string
              format: uuid
            amount_cents:
              type: integer
            fee_cents:
              type: integer
            net_amount_cents:
              type: integer
            paid_at:
              type: string
              format: date-time
    WebhookWithdrawalCompleted:
      type: object
      properties:
        type:
          type: string
          enum:
          - withdrawal.completed
        created_at:
          type: string
          format: date-time
        data:
          type: object
          properties:
            withdrawal_id:
              type: string
              format: uuid
            amount_cents:
              type: integer
            completed_at:
              type: string
              format: date-time
    WebhookWithdrawalFailed:
      type: object
      properties:
        type:
          type: string
          enum:
          - withdrawal.failed
        created_at:
          type: string
          format: date-time
        data:
          type: object
          properties:
            withdrawal_id:
              type: string
              format: uuid
            amount_cents:
              type: integer
            reason:
              type: string
    WebhookTransactionRefunded:
      type: object
      properties:
        event_id:
          type: string
          format: uuid
        type:
          type: string
          enum:
          - transaction.refunded
        version:
          type: string
          example: 2026-05-01
        created_at:
          type: string
          format: date-time
        data:
          type: object
          properties:
            transaction_id:
              type: string
              format: uuid
            amount_refunded_cents:
              type: integer
            reason:
              type: string
            refunded_at:
              type: string
              format: date-time
    WebhookTransactionHeld:
      description: Transaction colocada em hold (cautelar) pelo time de risk. UNICA no mercado · seller só vê quando liberado.
      type: object
      properties:
        event_id:
          type: string
          format: uuid
        type:
          type: string
          enum:
          - transaction.held
        version:
          type: string
          example: 2026-05-01
        created_at:
          type: string
          format: date-time
        data:
          type: object
          properties:
            transaction_id:
              type: string
              format: uuid
            held_until:
              type: string
              format: date-time
              nullable: true
              description: NULL = hold indefinido até decisão manual.
            reason:
              type: string
    WebhookChargebackCreated:
      type: object
      properties:
        event_id:
          type: string
          format: uuid
        type:
          type: string
          enum:
          - chargeback.created
        version:
          type: string
          example: 2026-05-01
        created_at:
          type: string
          format: date-time
        data:
          type: object
          properties:
            chargeback_id:
              type: string
              format: uuid
            transaction_id:
              type: string
              format: uuid
            amount_cents:
              type: integer
            reason_code:
              type: string
              example: fraudulent
            dispute_deadline:
              type: string
              format: date-time
    WebhookSellerApproved:
      type: object
      properties:
        event_id:
          type: string
          format: uuid
        type:
          type: string
          enum:
          - seller.approved
        version:
          type: string
          example: 2026-05-01
        created_at:
          type: string
          format: date-time
        data:
          type: object
          properties:
            seller_id:
              type: integer
            approved_at:
              type: string
              format: date-time
            mdr_pix:
              type: number
              description: 'Taxa MDR PIX em decimal (ex: 0.05 = 5%)'
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: UUID v4 ou string única (8-128 chars). Replay = mesma resposta cacheada por 24h.
      schema:
        type: string
        minLength: 8
        maxLength: 128
      example: a3b6e3f8-2f1d-4f6b-9e7c-9b4f5d1c3a2e
    PublicId:
      name: id
      in: path
      required: true
      description: UUID v4 público da Transaction ou Withdrawal.
      schema:
        type: string
        format: uuid
  responses:
    Unauthorized:
      description: Credenciais inválidas / faltando
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: unauthorized
            detail: invalid_credentials
    Forbidden:
      description: 'Operação proibida (ex: seller suspenso)'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: seller_not_approved
            message: Seller com status 'suspended' não pode operar.
    NotFound:
      description: Recurso não encontrado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: not_found
            message: Transação não encontrada.
    BadRequest:
      description: Request mal formada / Idempotency-Key faltando
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: idempotency_key_required
            message: Header Idempotency-Key é obrigatório (8-128 caracteres).
    ValidationError:
      description: Validação de campos falhou
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationErrorResponse'
    RateLimited:
      description: Rate limit atingido
      headers:
        Retry-After:
          schema:
            type: integer
          description: Segundos até poder tentar de novo.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: rate_limited
            message: Tente novamente em 60 segundos.
    AcquirerError:
      description: Adquirente externa retornou erro
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: acquirer_error
            message: ...
externalDocs:
  description: Site público
  url: 'https://throne.com.br/developers'
