openapi: 3.1.0
info:
  title: API Accelerazione
  version: "1.0.0"
  summary: Cobranças com Pix, cartão e Pix parcelado, estornos, webhooks, saldo, saques e extrato.
  description: |
    Contrato da API Accelerazione. Guias em português em /docs.

    - Autenticação: cabeçalho `Authorization` com a chave crua (`kpa_test_sk_…` no sandbox).
    - Valores em centavos (inteiros) em toda a API: cobranças, saldo, saques e extrato.
    - Endereços de produção são entregues na habilitação.
  contact:
    name: Equipe de integração Accelerazione
servers:
  - url: "https://g6idryo27h.execute-api.us-east-1.amazonaws.com"
    description: Sandbox. Mesmas rotas da produção; o Pix de saída dos saques é simulado.
  - url: "https://payments.kirvano.com"
    description: Produção.
security:
  - ApiKey: []
tags:
  - { name: Cobranças, description: Criar e consultar PaymentIntents. }
  - { name: Pix parcelado, description: Pré-análise da Pagaleve. }
  - { name: 3DS, description: Sessão de coleta de dados do dispositivo. }
  - {
      name: Tokenização,
      description: Sessão do SDK de campos e cartões salvos como token.,
    }
  - { name: Estornos, description: Devolução integral e assíncrona. }
  - {
      name: Saques,
      description: Transferências Pix do saldo da conta. Valores em centavos.,
    }
  - {
      name: Saldo e extrato,
      description: Saldo da conta por tipo e extrato da conta bancária vinculada. Valores em centavos.,
    }
  - { name: Webhooks, description: Notificações enviadas ao seu servidor. }
paths:
  /api/v1/paymentintents:
    post:
      operationId: createPaymentIntent
      tags: [Cobranças]
      summary: Criar cobrança
      description: |
        Cria uma cobrança (PaymentIntent) com Pix, cartão de crédito, cartão de débito ou Pix parcelado.
        `201` confirma a criação; o resultado está em `status`. Um cartão recusado também volta `201`, com `status: "Failed"`.
        Valores em centavos.
      x-required-scope: paymentintents:create
      parameters:
        - name: Idempotency-Key
          in: header
          description: 1 a 200 caracteres, uma por tentativa de compra. Retenção mínima de 24 horas. Recomendada em todos os métodos.
          schema:
            {
              type: string,
              minLength: 1,
              maxLength: 200,
              examples: [pedido-1001-tentativa-1],
            }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreatePaymentIntentRequest" }
            examples:
              pix:
                summary: Pix de R$ 19,90
                value:
                  amount: 1990
                  currency: BRL
                  paymentMethods: [{ type: Pix, amount: 1990 }]
                  paymentMethodOptions: { pix: { expiresInMinutes: 30 } }
                  customer:
                    { name: Cliente de Teste, email: cliente@example.com }
                  postbackUrl: https://loja.example.com/webhooks/dm
                  metadata: { orderId: pedido-pix-1001 }
              creditCard:
                summary: Crédito de R$ 100,00 em 3 parcelas com 3DS
                value:
                  amount: 10000
                  currency: BRL
                  paymentMethods:
                    - type: CreditCard
                      amount: 10000
                      card:
                        {
                          number: "5555555555554444",
                          expMonth: 12,
                          expYear: 2030,
                          cvc: "123",
                          holderName: MARIA SOUZA,
                        }
                  paymentMethodOptions:
                    creditCard:
                      {
                        installments: 3,
                        threeDSecure: automatic,
                        threeDs:
                          {
                            sessionToken: 3ds_sess_9J4W2C7X5N8QK2R4T6Y8V0H2M4P6A1B3,
                          },
                      }
                  customer:
                    name: Maria Souza
                    email: maria@example.com
                    document: "12345678909"
                    address: { zipCode: "01310100", country: BR }
                  split:
                    rules:
                      [
                        {
                          recipientId: ID_DO_RECEBEDOR,
                          type: percentage,
                          amount: 100,
                          options:
                            {
                              isPrincipal: true,
                              chargeMdr: true,
                              chargebackLiability: true,
                            },
                        },
                      ]
                  postbackUrl: https://loja.example.com/webhooks/dm
                  metadata: { orderId: pedido-card-1001 }
              pagaleve:
                summary: Pix parcelado de R$ 299,90
                value:
                  amount: 29990
                  currency: BRL
                  paymentMethods: [{ type: Pagaleve, amount: 29990 }]
                  customer:
                    {
                      name: Cliente de Teste,
                      email: cliente@example.com,
                      document: "12345678909",
                      phone: "+5511999999999",
                    }
                  paymentMethodOptions:
                    pagaleve:
                      {
                        approveUrl: https://loja.example.com/pedidos/1001/retorno,
                        cancelUrl: https://loja.example.com/pedidos/1001/cancelado,
                      }
                  postbackUrl: https://loja.example.com/webhooks/dm
                  metadata: { orderId: pedido-pagaleve-1001 }
      responses:
        "201":
          description: Cobrança criada. Leia `status` para saber o resultado.
          headers:
            X-Request-Id: { $ref: "#/components/headers/XRequestId" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaymentIntent" }
              examples:
                pix: { $ref: "#/components/examples/PaymentIntentPix" }
                desafio3ds:
                  { $ref: "#/components/examples/PaymentIntentRequiresAction" }
                recusado: { $ref: "#/components/examples/PaymentIntentFailed" }
        "400":
          description: Requisição inválida. O código está em `type`. Veja o catálogo em /docs/erros/codigos.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
              example:
                {
                  title: PaymentIntent Creation Failed,
                  detail: "amount must be an integer in the currency's minor unit",
                  status: 400,
                  type: amount_must_be_integer_minor_units,
                }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409":
          description: "`idempotency_key_in_flight` (respeite `Retry-After`) ou `idempotency_key_payload_mismatch`."
          headers:
            Retry-After: { schema: { type: integer } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "422":
          description: "Não processável nas condições atuais: `merchant_not_eligible`, `PAYMENT_METHOD_NOT_ENABLED`, `merchant_currency_not_enabled`, `card_token_not_found`, `card_token_not_usable` e outros."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "500":
          description: "`pix_generation_failed` ou falha inesperada. Repita com a mesma chave."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "503":
          description: "`temporarily_unavailable`, `vault_unavailable` ou `idempotency_unavailable`. Traz `Retry-After`."
          headers:
            Retry-After: { schema: { type: integer } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
  /api/v1/paymentintents/{id}:
    get:
      operationId: getPaymentIntent
      tags: [Cobranças]
      summary: Consultar cobrança
      description: Devolve o estado atual da cobrança. Logo após a criação ou um webhook, pode devolver estado anterior ou `404` temporário.
      x-required-scope: paymentintents:read
      parameters:
        - $ref: "#/components/parameters/PaymentIntentId"
      responses:
        "200":
          description: A cobrança.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PaymentIntent" }
              examples:
                pago: { $ref: "#/components/examples/PaymentIntentSucceeded" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404":
          description: "`PaymentIntentNotFound`. Um `id` fora do formato UUID responde `404` sem corpo."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "500":
          description: "`PaymentIntentRetrievalError`. Repita."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
  /api/v1/paymentintents/{id}/refund-requests:
    post:
      operationId: requestRefund
      tags: [Estornos]
      summary: Solicitar estorno
      description: |
        Pede o estorno **integral** e assíncrono de uma cobrança `Succeeded`. `202` confirma o pedido, não a devolução.
        O desfecho chega pelos webhooks `payment_intent.refunded` e `payment_intent.refund_failed`.
        A chave de idempotência vai no **corpo**.
      x-required-scope: paymentintents:create
      parameters:
        - $ref: "#/components/parameters/PaymentIntentId"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RefundRequest" }
            example:
              {
                reason: Cancelamento solicitado pelo comprador,
                idempotencyKey: estorno-pedido-1001-tentativa-1,
              }
      responses:
        "202":
          description: Pedido aceito. Com estorno já em andamento, devolve o mesmo `refundId`.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RefundAccepted" }
              example: { refundId: rfnd_EXEMPLO, status: requested }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404":
          description: "`PaymentIntentNotFound`."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "409":
          description: "`AlreadyRefunded`: o estorno já foi concluído."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "422":
          description: "`PaymentIntentNotRefundable`: a cobrança não está `Succeeded`."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "500":
          description: Falha inesperada. Repita com a mesma `idempotencyKey`.
  /api/v1/pagaleve/pre-analysis:
    post:
      operationId: pagalevePreAnalysis
      tags: [Pix parcelado]
      summary: Pré-análise do Pix parcelado
      description: Indica se o Pix parcelado deve ser oferecido a um comprador e valor. Não reserva crédito nem garante aprovação.
      x-required-scope: pagaleve:create
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PagalevePreAnalysisRequest" }
            example:
              {
                amount: 29990,
                customer:
                  {
                    document: "12345678909",
                    email: cliente@example.com,
                    phone: "+5511999999999",
                  },
              }
      responses:
        "200":
          description: Resultado.
          content:
            application/json:
              schema:
                type: object
                properties:
                  action:
                    {
                      type: string,
                      enum: [ALLOW, DENY],
                      description: Trate valores desconhecidos como DENY.,
                    }
              example: { action: ALLOW }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422":
          description: "`merchant_not_eligible`."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "502":
          description: "`pagaleve_pre_analysis_failed` ou `merchant_eligibility_check_failed`. Não trate como ALLOW."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
  /v1/3ds/device-data-sessions:
    post:
      operationId: createThreeDsSession
      tags: [3DS]
      summary: Abrir sessão 3DS
      description: |
        Abre a sessão de coleta de dados do dispositivo para uma tentativa de cobrança de crédito.
        O `session_token` vai ao navegador (SDK `DM3DS`) e volta na criação da cobrança em
        `paymentMethodOptions.creditCard.threeDs.sessionToken`. Validade de 30 minutos, uso único.

        Esta rota usa `snake_case`. Erros trazem o código em `title`.
      x-required-scope: 3ds:create
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateThreeDsSessionRequest" }
            example:
              { amount: 10000, currency: BRL, transaction_scope: Domestic }
      responses:
        "201":
          description: Sessão aberta.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ThreeDsSession" }
              example:
                {
                  session_token: 3ds_sess_9J4W2C7X5N8QK2R4T6Y8V0H2M4P6A1B3,
                  expires_at: "2026-09-08T15:30:00.1234567Z",
                }
        "400":
          description: "`amount_must_be_integer_minor_units`, `THREE_DS_SESSION_AMOUNT_INVALID`, `THREE_DS_SESSION_CURRENCY_NOT_SUPPORTED` (em `title`), ou `forbidden_field` (em `error`)."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetailsTitleCode" }
              example:
                {
                  title: THREE_DS_SESSION_CURRENCY_NOT_SUPPORTED,
                  detail: "Moeda 'BRX' não é suportada.",
                  status: 400,
                }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422":
          description: "`THREE_DS_SESSION_SCOPE_INVALID`: `transaction_scope` diferente de `Domestic` ou `Offshore`."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetailsTitleCode" }
        "429":
          description: Excesso momentâneo de requisições. Sem corpo e sem `Retry-After`; repita com espera crescente.
        "500":
          description: "`THREE_DS_SESSION_CONFLICT`: abra outra sessão."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetailsTitleCode" }
        "503":
          description: Falha temporária ao preparar a coleta. Nada foi gravado; repita.
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetailsTitleCode" }
  /v1/card-token-sessions:
    post:
      operationId: createCardTokenSession
      tags: [Tokenização]
      summary: Abrir sessão do SDK de campos
      description: |
        Abre a sessão que autoriza o SDK de campos (`DMCard`) a capturar o cartão no navegador do comprador.
        Chame do seu servidor, uma vez por checkout. A sessão vale 1 hora e permite até 5 tentativas de tokenização.
        Disponível no sandbox e em produção, com habilitação por conta. Guia: /docs/cartoes/sdk-de-campos.
      x-required-scope: card-token-sessions:create
      requestBody:
        required: false
        content:
          application/json:
            schema:
              {
                type: object,
                description: Corpo vazio. A conta vem da chave de API.,
              }
            example: {}
      responses:
        "201":
          description: Sessão aberta. Entregue `session_token` ao navegador.
          headers:
            Cache-Control: { schema: { type: string, examples: [no-store] } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CardTokenSession" }
              example:
                {
                  session_token: cts_9J4W2C7X5N8QK2R4T6Y8V0H2M4P6A1B3,
                  expires_at: "2026-11-02T20:06:00Z",
                  max_tokens: 5,
                }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403":
          description: 'Chave sem a permissão (`{"message":"Forbidden"}`), conta não habilitada (`card_sdk_not_enabled`) ou configuração de cartão incompatível (`card_sdk_3ds_mode_unsupported`).'
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CardSdkError" }
              example: { code: card_sdk_not_enabled }
        "429":
          description: "Excesso momentâneo de requisições (`rate_limited`). Traz `Retry-After`."
          headers:
            Retry-After: { schema: { type: integer } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CardSdkError" }
        "503":
          description: "`session_store_unavailable`: indisponibilidade temporária. Traz `Retry-After`."
          headers:
            Retry-After: { schema: { type: integer } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CardSdkError" }
  /api/v1/card-tokens:
    post:
      operationId: createCardToken
      tags: [Tokenização]
      summary: Tokenizar cartão
      description: |
        Troca os dados do cartão por um token reutilizável (`ctok_…`). Não cobra nada.
        Se o mesmo cartão já tem token ativo para o mesmo `customerId`, responde `200` com o token existente e `deduplicated: true`.

        Nesta rota o código de segurança é `cvv` (não `cvc`) e os erros usam o envelope `{code, message}`.
        Disponível no sandbox; confirme a habilitação em produção com a equipe de integração.
      x-required-scope: card-tokens:create
      parameters:
        - name: Idempotency-Key
          in: header
          description: "Obrigatória (ou `idempotencyKey` no corpo). 8 a 255 caracteres `[A-Za-z0-9_.:-]`. Retenção de 24 horas."
          schema:
            {
              type: string,
              minLength: 8,
              maxLength: 255,
              pattern: "^[A-Za-z0-9_.:-]+$",
            }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateCardTokenRequest" }
            example:
              card:
                {
                  number: "5555555555554444",
                  expMonth: 12,
                  expYear: 2030,
                  cvv: "123",
                  holderName: MARIA SOUZA,
                }
              customerId: cliente-1001
      responses:
        "201":
          description: Token criado.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CardToken" }
              example:
                {
                  cardTokenUid: ctok_01M0FVG11PZK94T569VFYE4FY3,
                  brand: MASTERCARD,
                  bin: "555555",
                  last4: "4444",
                  expMonth: 12,
                  expYear: 2030,
                  status: ACTIVE,
                  cvvStored: true,
                  deduplicated: false,
                }
        "200":
          description: Cartão já tokenizado para o mesmo cliente. Devolve o token existente.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CardToken" }
        "400":
          description: "`validation_failed` ou `invalid_card`. JSON malformado ou campo obrigatório ausente vem em Problem Details, sem `code`."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CodeMessageError" }
              example:
                {
                  code: validation_failed,
                  message: "Field 'card.cvc' is not supported; use 'card.cvv' for the card security code.",
                }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "409":
          description: "`idempotency_conflict` (mesma chave, conteúdo diferente) ou `idempotency_in_flight` (repetição enquanto a primeira processa)."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CodeMessageError" }
        "429":
          description: "`rate_limited`: excesso momentâneo de requisições. Traz `Retry-After`."
          headers:
            Retry-After: { schema: { type: integer } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CodeMessageError" }
        "503":
          description: "`vault_unavailable`: indisponibilidade temporária. Repita preservando chave e corpo."
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CodeMessageError" }
  /api/v1/withdrawals:
    post:
      operationId: createWithdrawal
      tags: [Saques]
      summary: Solicitar saque
      description: |
        Cria um pedido de saque por Pix para a chave de destino cadastrada da conta. O valor é reservado
        no saldo e o pedido entra em análise. `201` confirma o pedido, não o envio do Pix.

        `amount` é inteiro em centavos, como nas cobranças: `25000` é R$ 250,00. Decimal ou texto
        recebem `400 withdrawal.validation_failed`. No sandbox, o Pix de saída é simulado.
      x-required-scope: withdrawals:create
      parameters:
        - $ref: "#/components/parameters/WithdrawalIdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateWithdrawalRequest" }
            example:
              accountId: 3f6b2c1e-8d4a-4b7f-9e21-5a0c7d9e1b42
              amount: 25000
              currencyCode: BRL
              pixKeyType: CNPJ
              pixKey: "11222333000181"
      responses:
        "201":
          description: Saque criado e em análise.
          headers:
            Location:
              description: Caminho do saque criado.
              schema:
                {
                  type: string,
                  example: /api/v1/withdrawals/01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47,
                }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WithdrawalCreated" }
              example:
                {
                  withdrawalId: 01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47,
                  status: processando,
                }
        "200":
          description: Repetição idempotente. Devolve o saque original com o status atual.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WithdrawalCreated" }
              example:
                {
                  withdrawalId: 01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47,
                  status: transferido,
                }
        "400": { $ref: "#/components/responses/WithdrawalBadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/WithdrawalForbidden" }
        "409":
          description: "`withdrawal.idempotency_conflict`: a chave já foi usada com outro valor, chave Pix ou conta."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
              example:
                {
                  type: withdrawal.idempotency_conflict,
                  title: Withdrawal creation failed,
                  status: 409,
                  detail: This idempotency key was already used with different parameters.,
                }
        "422":
          description: |
            Regra de negócio: `withdrawal.insufficient_balance`, `withdrawal.invalid_account`,
            `withdrawal.provider_account_not_configured`, `withdrawal.destination_not_configured`,
            `withdrawal.destination_not_owned`, `withdrawal.source_is_master_account`,
            `withdrawal.amount_limit_exceeded`, `withdrawal.daily_count_limit_exceeded`,
            `withdrawal.daily_amount_limit_exceeded`.
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
              example:
                {
                  type: withdrawal.insufficient_balance,
                  title: Withdrawal creation failed,
                  status: 422,
                  detail: Insufficient balance.,
                }
        "429": { $ref: "#/components/responses/WithdrawalRateLimited" }
        "500": { $ref: "#/components/responses/WithdrawalInternalError" }
        "503": { $ref: "#/components/responses/IpAllowlistUnavailable" }
    get:
      operationId: listWithdrawals
      tags: [Saques]
      summary: Listar saques
      description: Lista os saques da conta, do mais recente para o mais antigo, com paginação por número de página.
      x-required-scope: withdrawals:read
      parameters:
        - name: status
          in: query
          schema: { $ref: "#/components/schemas/WithdrawalStatus" }
        - name: dateFrom
          in: query
          description: Criados a partir desta data. Sem fuso, vale UTC.
          schema: { type: string, examples: ["2026-11-01"] }
        - name: dateTo
          in: query
          description: Criados até esta data. Data sem hora vai até 23:59:59 UTC. Precisa ser maior ou igual a `dateFrom`.
          schema: { type: string, examples: ["2026-11-02"] }
        - name: page
          in: query
          schema: { type: integer, minimum: 1, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      responses:
        "200":
          description: Página de saques.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WithdrawalList" }
        "400":
          description: "`withdrawal.invalid_status`, `invalid_page_size` ou `withdrawal.validation`."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/WithdrawalForbidden" }
        "429": { $ref: "#/components/responses/WithdrawalRateLimited" }
        "500": { $ref: "#/components/responses/WithdrawalInternalError" }
  /api/v1/withdrawals/{id}:
    get:
      operationId: getWithdrawal
      tags: [Saques]
      summary: Consultar saque
      description: Devolve o estado atual de um saque. Saque de outra conta responde `404`, igual a um inexistente.
      x-required-scope: withdrawals:read
      parameters:
        - $ref: "#/components/parameters/WithdrawalId"
      responses:
        "200":
          description: O saque.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Withdrawal" }
              example:
                {
                  id: 01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47,
                  status: transferido,
                  amount: 25000,
                  currencyCode: BRL,
                  pixKeyType: CNPJ,
                  pixKey: "************81",
                  createdAt: "2026-11-02T14:58:03.412718Z",
                  pixEndToEndId: E00000000202611021508Ab12Cd34Ef5,
                  transferredAt: "2026-11-02T15:08:41.090233Z",
                  rejectedAt: null,
                  rejectionReason: null,
                  rejectionSource: null,
                  failedAt: null,
                  failureCode: null,
                  failureMessage: null,
                }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/WithdrawalForbidden" }
        "404": { $ref: "#/components/responses/WithdrawalNotFound" }
        "429": { $ref: "#/components/responses/WithdrawalRateLimited" }
        "500": { $ref: "#/components/responses/WithdrawalInternalError" }
  /api/v1/withdrawals/{id}/cancel:
    post:
      operationId: cancelWithdrawal
      tags: [Saques]
      summary: Cancelar saque
      description: |
        Cancela um saque ainda em análise. Idempotente. A devolução do valor ao saldo é assíncrona.
        Se o saque já foi aprovado, responde `409 withdrawal.cannot_cancel`.
      x-required-scope: withdrawals:create
      parameters:
        - $ref: "#/components/parameters/WithdrawalId"
      responses:
        "200":
          description: Saque cancelado.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WithdrawalCancelled" }
              example:
                {
                  withdrawalId: 01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47,
                  status: cancelado,
                  reversalTransactionId: null,
                }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/WithdrawalForbidden" }
        "404": { $ref: "#/components/responses/WithdrawalNotFound" }
        "409":
          description: "`withdrawal.cannot_cancel`: o saque já foi aprovado ou chegou a um estado final."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
              example:
                {
                  type: withdrawal.cannot_cancel,
                  title: Withdrawal cancel failed,
                  status: 409,
                  detail: Withdrawal can only be cancelled while pending.,
                }
        "429": { $ref: "#/components/responses/WithdrawalRateLimited" }
        "500": { $ref: "#/components/responses/WithdrawalInternalError" }
  /api/v1/balance:
    get:
      operationId: getBalance
      tags: [Saldo e extrato]
      summary: Consultar saldo
      description: |
        Saldo da conta interna, por tipo: `Available` (livre para saque), `Blocked`, `PendingSettlement`,
        `Reserved` e `PendingWithdrawal`. `amount` é inteiro na menor unidade da moeda da conta (BRL ou USD).
        O `accountId` precisa pertencer à conta da chave; outra conta responde `404`. Sujeito à lista de IPs
        da conta, como os saques. Guia: /docs/saques/saldo.
      x-required-scope: balance:read
      parameters:
        - name: accountId
          in: query
          required: true
          description: UUID da conta de saldo, entregue no credenciamento. O mesmo do saque.
          schema:
            { type: string, format: uuid, examples: ["3f6b2c1e-8d4a-4b7f-9e21-5a0c7d9e1b42"] }
        - name: balanceType
          in: query
          required: false
          description: Filtra um tipo de saldo. Maiúsculas e minúsculas são indiferentes.
          schema:
            {
              type: string,
              enum: [Available, Blocked, PendingSettlement, Reserved, PendingWithdrawal],
            }
      responses:
        "200":
          description: Saldo por tipo. Um tipo sem registro pode faltar; trate como zero.
          headers:
            Cache-Control: { schema: { type: string, examples: [no-store] } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Balance" }
              example:
                {
                  accountId: 3f6b2c1e-8d4a-4b7f-9e21-5a0c7d9e1b42,
                  balances:
                    [
                      { balanceType: Available, amount: 10550, currencyCode: BRL, updatedAt: "2026-11-03T12:00:00Z" },
                      { balanceType: Blocked, amount: 1200, currencyCode: BRL, updatedAt: "2026-11-03T12:00:00Z" },
                      { balanceType: PendingWithdrawal, amount: 150, currencyCode: BRL, updatedAt: "2026-11-03T12:00:00Z" },
                    ],
                }
        "400":
          description: "`balance.invalid_request` (`accountId` ausente), `balance.invalid_type` (`balanceType` desconhecido) ou erro de formato (`type` em URL) para `accountId` fora do formato UUID."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
              example: { type: balance.invalid_type, title: Unknown balanceType., status: 400 }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/WithdrawalForbidden" }
        "404":
          description: "`balance.account_not_found`: conta inexistente ou de outra conta."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
              example: { type: balance.account_not_found, title: Account was not found., status: 404 }
        "422":
          description: "`balance.unsupported_currency`: a conta não é BRL nem USD."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
        "503":
          description: "`balance.unavailable` (repita com espera crescente) ou `ip_allowlist_unavailable` (repita em alguns segundos)."
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/ProblemDetails" }
  /api/bank-accounts/{accountNumber}/statement:
    get:
      operationId: getBankAccountStatement
      tags: [Saldo e extrato]
      summary: Consultar extrato bancário
      description: |
        Saldo no provedor, total agendado e movimentações de uma conta bancária vinculada à sua conta
        ou a um recebedor seu. `accountNumber` é o número da conta no provedor, não o `accountId` do saldo.
        Valores em centavos. Paginação por cursor: a ausência de `nextCursor` é o único sinal de fim.
        Sem conta bancária vinculada, responde `404 Statement.BankAccountNotFound`.
      x-required-scope: bank-accounts:read
      parameters:
        - $ref: "#/components/parameters/AccountNumber"
        - name: from
          in: query
          description: Início do período, ISO 8601 com fuso.
          schema:
            {
              type: string,
              format: date-time,
              examples: ["2026-10-01T00:00:00-03:00"],
            }
        - name: to
          in: query
          description: Fim do período, ISO 8601 com fuso.
          schema:
            {
              type: string,
              format: date-time,
              examples: ["2026-10-31T23:59:59-03:00"],
            }
        - name: limit
          in: query
          description: Itens por página. Acima de 200 é reduzido para 200.
          schema: { type: integer, default: 50, maximum: 200 }
        - name: cursor
          in: query
          description: "`nextCursor` da página anterior. Quando presente, `from`, `to` e `limit` são ignorados."
          schema: { type: string }
      responses:
        "200":
          description: Página do extrato.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Statement" }
              example:
                accountNumber: "29600685"
                merchantId: a3b2c1d0-5e6f-4a7b-8c9d-0e1f2a3b4c5d
                period:
                  {
                    from: "2026-10-01T00:00:00-03:00",
                    to: "2026-10-31T23:59:59-03:00",
                  }
                currentBalance: { amount: 183427, currency: BRL }
                scheduled: { amount: 0, currency: BRL }
                entries:
                  - {
                      id: stme-AAECAwQFBgc,
                      postedAt: "2026-10-30T18:32:05-03:00",
                      direction: credit,
                      amount: { amount: 14990, currency: BRL },
                      description: Crédito PIX Maria Souza,
                    }
                hasMore: true
                nextCursor: stmc-eyJvIjoyfQ
        "400": { $ref: "#/components/responses/StatementError" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/StatementError" }
        "502": { $ref: "#/components/responses/StatementError" }
        "503": { $ref: "#/components/responses/StatementError" }
        "504": { $ref: "#/components/responses/StatementError" }
  /api/bank-accounts/{accountNumber}/statement/{entryId}:
    get:
      operationId: getStatementEntry
      tags: [Saldo e extrato]
      summary: Consultar comprovante de lançamento
      description: Detalhe de um lançamento. Hoje só Pix recebido tem comprovante; os demais respondem `422 Statement.DetailNotAvailable`, definitivo.
      x-required-scope: bank-accounts:read
      parameters:
        - $ref: "#/components/parameters/AccountNumber"
        - name: entryId
          in: path
          required: true
          schema: { type: string, examples: [stme-AAECAwQFBgc] }
      responses:
        "200":
          description: Comprovante.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StatementEntryDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/StatementError" }
        "422": { $ref: "#/components/responses/StatementError" }
        "502": { $ref: "#/components/responses/StatementError" }
        "503": { $ref: "#/components/responses/StatementError" }
        "504": { $ref: "#/components/responses/StatementError" }
webhooks:
  paymentIntentEvent:
    post:
      operationId: paymentIntentWebhook
      tags: [Webhooks]
      summary: Evento de cobrança
      description: |
        Enviado à `postbackUrl` da cobrança quando ela chega a um desfecho. Responda **exatamente 200** depois de gravar o evento.
        Até três tentativas, com cerca de 60 segundos entre elas. Verifique `X-DM-Signature` (HMAC-SHA256 de `t + "." + corpo cru`).
      parameters:
        - name: X-DM-Signature
          in: header
          description: "`t=<unix>,v1=<hex>[,v1=<hex>]`. Ausente se a conta não tem segredo ativo."
          schema: { type: string }
        - name: X-DM-Webhook-Id
          in: header
          description: Identificador estável da entrega, para deduplicação.
          schema: { type: string, format: uuid }
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookEvent" }
            example:
              eventType: payment_intent.succeeded
              data:
                {
                  id: 8b727267-bf39-48a4-91ea-eb0780cb48a9,
                  object: payment_intent,
                  amount: 1990,
                  currency: BRL,
                  status: Succeeded,
                  metadata: { orderId: pedido-pix-1001 },
                }
      responses:
        "200":
          description: Evento recebido. Qualquer outro status gera reenvio.
components:
  examples:
    PaymentIntentPix:
      summary: Pix aguardando pagamento
      value:
        id: 8b727267-bf39-48a4-91ea-eb0780cb48a9
        object: payment_intent
        amount: 1990
        currency: BRL
        status: Processing
        statusDetail: null
        merchantId: 3c1d6a2e-5b7f-4e8a-9c0d-1e2f3a4b5c6d
        paymentMethods:
          [
            {
              type: Pix,
              amount: 1990,
              currency: BRL,
              card: null,
              pix: { pixKeyType: RANDOM, expiresInMinutes: 30 },
            },
          ]
        customer:
          {
            name: Cliente de Teste,
            email: cliente@example.com,
            document: null,
            phone: null,
            birthDate: null,
            address: null,
          }
        split: null
        metadata: { orderId: pedido-pix-1001 }
        lastPaymentError: null
        nextAction: null
        pixPayment:
          {
            qrCodeEmv: "00020126...",
            qrCodeUrl: "",
            pixCopyPaste: "00020126...",
            expiresAt: "2026-09-08T15:30:00Z",
            endToEndId: "",
            status: PENDING,
          }
        pagalevePayment: null
        postbackUrl: https://loja.example.com/webhooks/dm
        created: "2026-09-08T15:00:00Z"
        succeededAt: null
    PaymentIntentRequiresAction:
      summary: Cartão com desafio 3DS
      value:
        id: e092c6cc-8d6f-47d3-8616-df99670286dd
        object: payment_intent
        amount: 10000
        currency: BRL
        status: RequiresAction
        statusDetail: PENDING_CHALLENGE
        paymentMethods:
          - type: CreditCard
            amount: 10000
            currency: BRL
            card:
              {
                maskedNumber: "**** **** **** 4444",
                expMonth: 12,
                expYear: 2030,
                holderName: MARIA SOUZA,
                brand: MASTERCARD,
                lastFourDigits: "4444",
                bin: "555555",
                threeDs: null,
              }
            pix: null
        lastPaymentError: null
        nextAction:
          {
            type: IFRAME_CHALLENGE,
            url: "https://hooks.example.com/v1/3ds/challenge?id=6c868afc-d6bc-45c4-9886-ed97d20a2729",
            authenticationId: 6c868afc-d6bc-45c4-9886-ed97d20a2729,
            expiresAt: "2026-09-08T15:10:00Z",
          }
        metadata: { orderId: pedido-card-1001 }
        created: "2026-09-08T15:00:00Z"
        succeededAt: null
    PaymentIntentFailed:
      summary: Cartão recusado
      value:
        id: e092c6cc-8d6f-47d3-8616-df99670286dd
        object: payment_intent
        amount: 10000
        currency: BRL
        status: Failed
        lastPaymentError:
          {
            code: "51",
            message: Saldo insuficiente,
            category: issuer_decline,
            providerErrorCode: "51",
          }
        nextAction: null
        metadata: { orderId: pedido-card-1001 }
        created: "2026-09-08T15:00:00Z"
        succeededAt: null
    PaymentIntentSucceeded:
      summary: Pix pago
      value:
        id: 8b727267-bf39-48a4-91ea-eb0780cb48a9
        object: payment_intent
        amount: 1990
        currency: BRL
        status: Succeeded
        paymentMethods:
          [
            {
              type: Pix,
              amount: 1990,
              currency: BRL,
              card: null,
              pix: { pixKeyType: RANDOM, expiresInMinutes: 30 },
            },
          ]
        metadata: { orderId: pedido-pix-1001 }
        pixPayment:
          {
            qrCodeEmv: "00020126...",
            qrCodeUrl: "",
            pixCopyPaste: "00020126...",
            expiresAt: "2026-09-08T15:30:00Z",
            endToEndId: E00000000202609081504A1b2C3d4E5f,
            status: PENDING,
          }
        created: "2026-09-08T15:00:00Z"
        succeededAt: "2026-09-08T15:04:12Z"
  headers:
    XRequestId:
      description: Identificador da requisição (`req_` + 32 hex). Guarde para o suporte.
      schema: { type: string }
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: Authorization
      description: |
        A chave de API crua, sem prefixo: `kpa_test_sk_…` no sandbox e `kpa_live_sk_…` em produção.
        Use apenas no servidor.
  parameters:
    PaymentIntentId:
      name: id
      in: path
      required: true
      schema:
        {
          type: string,
          format: uuid,
          examples: [8b727267-bf39-48a4-91ea-eb0780cb48a9],
        }
    WithdrawalIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: 10 a 200 caracteres, única por pedido de saque. Alternativa ao campo `idempotencyKey` do corpo; o cabeçalho tem precedência.
      schema:
        {
          type: string,
          minLength: 10,
          maxLength: 200,
          examples: [saque-2026-11-02-000123],
        }
    WithdrawalId:
      name: id
      in: path
      required: true
      schema:
        {
          type: string,
          format: uuid,
          examples: [01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47],
        }
    AccountNumber:
      name: accountNumber
      in: path
      required: true
      description: Número da conta do recebedor, sem dígito verificador.
      schema: { type: string, examples: ["29600685"] }
  responses:
    Unauthorized:
      description: Cabeçalho `Authorization` ausente. Corpo da borda da API.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayError" }
          example: { message: Unauthorized }
    Forbidden:
      description: A chave não tem permissão para o recurso.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayError" }
          example: { message: Forbidden }
    WithdrawalForbidden:
      description: 'Chave sem permissão (`{"message":"Forbidden"}`) ou IP fora da lista permitida (`type: ip_not_allowed`).'
      content:
        application/json:
          schema:
            oneOf:
              - $ref: "#/components/schemas/GatewayError"
              - $ref: "#/components/schemas/ProblemDetails"
          example:
            {
              type: ip_not_allowed,
              title: IP not allowed,
              status: 403,
              detail: Your source IP is not in the allow-list configured for this merchant and service.,
            }
    WithdrawalBadRequest:
      description: |
        `idempotency_key_required`, `withdrawal.validation_failed`, ou erro de formato do JSON
        (`type` em URL, detalhes em `errors`; inclui `pixKeyType` desconhecido como `EVP`).
      content:
        application/problem+json:
          schema: { $ref: "#/components/schemas/ProblemDetails" }
          example:
            {
              type: withdrawal.validation_failed,
              title: Withdrawal creation failed,
              status: 400,
              detail: "Request validation failed: Amount: Amount must be at least 100 (R$ 1,00).",
            }
    WithdrawalNotFound:
      description: "`withdrawal.not_found`: saque inexistente ou de outra conta. `id` fora do formato UUID responde `404` sem corpo."
      content:
        application/problem+json:
          schema: { $ref: "#/components/schemas/ProblemDetails" }
    WithdrawalRateLimited:
      description: Excesso momentâneo de requisições nas rotas de saque. Sem corpo e sem `Retry-After`; repita com espera crescente.
    WithdrawalInternalError:
      description: Falha inesperada. Na criação, repita com a mesma `Idempotency-Key` e o mesmo corpo.
      content:
        application/problem+json:
          schema: { $ref: "#/components/schemas/ProblemDetails" }
    IpAllowlistUnavailable:
      description: "`ip_allowlist_unavailable`: a lista de IPs não pôde ser verificada. Repita em alguns segundos."
      content:
        application/problem+json:
          schema: { $ref: "#/components/schemas/ProblemDetails" }
    StatementError:
      description: Erro do extrato, com `retryable` indicando se vale repetir.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/StatementErrorBody" }
          example:
            {
              error: Statement.BankAccountNotFound,
              message: "Bank account '29600685' was not found.",
              retryable: false,
              requestId: "0HN7Q2S1A8C3K:00000002",
            }
  schemas:
    GatewayError:
      type: object
      description: Erro respondido pela borda da API, antes do serviço.
      properties:
        message: { type: string, examples: [Unauthorized] }
    ProblemDetails:
      type: object
      description: Problem Details (RFC 9457). O código estável está em `type`.
      properties:
        type: { type: string, description: Código do erro. }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }
        errors:
          type: object
          description: Presente em erros de formato do corpo.
          additionalProperties: { type: array, items: { type: string } }
        traceId: { type: string }
    ProblemDetailsTitleCode:
      type: object
      description: Problem Details com o código do erro em `title` (rotas de sessão 3DS).
      properties:
        title: { type: string, description: Código do erro. }
        detail: { type: string }
        status: { type: integer }
    CodeMessageError:
      type: object
      description: Envelope de erro da tokenização.
      required: [code]
      properties:
        code:
          {
            type: string,
            enum:
              [
                validation_failed,
                invalid_card,
                idempotency_conflict,
                idempotency_in_flight,
                rate_limited,
                vault_unavailable,
                idempotency_replay_corrupted,
              ],
          }
        message: { type: string }
    StatementErrorBody:
      type: object
      required: [error, retryable]
      properties:
        error:
          type: string
          enum:
            [
              Statement.InvalidPeriod,
              Statement.InvalidCursor,
              Statement.BankAccountNotFound,
              Statement.EntryNotFound,
              Statement.DetailNotAvailable,
              Statement.ProviderError,
              AccountsGateway.Unavailable,
              AccountsGateway.Timeout,
            ]
        message: { type: string }
        retryable: { type: boolean }
        requestId: { type: [string, "null"] }
    CreateThreeDsSessionRequest:
      type: object
      required: [amount, currency]
      properties:
        amount:
          {
            type: integer,
            minimum: 1,
            description: Valor na menor unidade da moeda. Igual ao `amount` da cobrança.,
          }
        currency:
          {
            type: string,
            examples: [BRL, USD],
            description: Igual à moeda da cobrança.,
          }
        transaction_scope:
          {
            type: string,
            enum: [Domestic, Offshore],
            default: Domestic,
            description: Escopo da conta definido no credenciamento. Não depende da moeda.,
          }
    ThreeDsSession:
      type: object
      required: [session_token, expires_at]
      properties:
        session_token:
          {
            type: string,
            pattern: "^3ds_sess_[0-9A-HJKMNP-TV-Z]{32}$",
            examples: [3ds_sess_9J4W2C7X5N8QK2R4T6Y8V0H2M4P6A1B3],
          }
        expires_at:
          {
            type: string,
            format: date-time,
            description: Até sete casas de fração de segundo.,
          }
    CreateCardTokenRequest:
      type: object
      required: [card]
      properties:
        card:
          type: object
          required: [number, expMonth, expYear]
          properties:
            number:
              { type: string, description: "12 a 19 dígitos, Luhn válido." }
            expMonth: { type: integer, minimum: 1, maximum: 12 }
            expYear: { type: integer, minimum: 2000, maximum: 2100 }
            cvv:
              {
                type: string,
                pattern: "^[0-9]{3,4}$",
                description: Opcional. `cvc` é recusado nesta rota.,
              }
            holderName: { type: string }
        customerId:
          {
            type: string,
            description: Identificador estável do cliente no seu sistema. Separa os cartões de clientes diferentes.,
          }
        idempotencyKey:
          {
            type: string,
            description: "Alternativa ao cabeçalho. Se enviar os dois, precisam ser iguais.",
          }
    CardTokenSession:
      type: object
      required: [session_token, expires_at, max_tokens]
      properties:
        session_token: { type: string, pattern: "^cts_[0-9A-HJKMNP-TV-Z]{32}$" }
        expires_at: { type: string, format: date-time }
        max_tokens:
          {
            type: integer,
            description: Tentativas de tokenização permitidas na sessão.,
            examples: [5],
          }
    CardSdkError:
      type: object
      required: [code]
      properties:
        code:
          {
            type: string,
            enum:
              [
                card_sdk_not_enabled,
                card_sdk_3ds_mode_unsupported,
                rate_limited,
                session_store_unavailable,
              ],
          }
        message: { type: string }
    CardToken:
      type: object
      properties:
        cardTokenUid:
          {
            type: string,
            pattern: "^ctok_[0-9A-HJKMNP-TV-Z]{26}$",
            examples: [ctok_01M0FVG11PZK94T569VFYE4FY3],
          }
        brand:
          {
            type: string,
            enum:
              [
                VISA,
                MASTERCARD,
                ELO,
                HIPERCARD,
                AMERICAN_EXPRESS,
                DINERS,
                DISCOVER,
                JCB,
                UNSPECIFIED,
              ],
          }
        bin:
          {
            type: string,
            description: Seis primeiros dígitos.,
            examples: ["555555"],
          }
        last4: { type: string, examples: ["4444"] }
        expMonth: { type: integer }
        expYear: { type: integer }
        status: { type: string, enum: [ACTIVE, SUSPENDED] }
        cvvStored: { type: boolean }
        deduplicated: { type: boolean }
    PixKeyTypeRequest:
      type: string
      enum: [CPF, CNPJ, Email, Phone, Random]
      description: Tipo da chave no pedido. Chave aleatória é `Random`; `EVP` é recusado.
    CreateWithdrawalRequest:
      type: object
      required: [accountId, amount, currencyCode, pixKeyType, pixKey]
      properties:
        accountId:
          {
            type: string,
            format: uuid,
            description: "Conta de origem, entregue no credenciamento.",
          }
        amount:
          {
            type: integer,
            minimum: 100,
            description: "Valor em **centavos**. `25000` é R$ 250,00. Mínimo `100` (R$ 1,00).",
          }
        currencyCode: { type: string, const: BRL }
        pixKeyType: { $ref: "#/components/schemas/PixKeyTypeRequest" }
        pixKey:
          {
            type: string,
            description: Chave Pix de destino. Precisa ser a cadastrada como destino de saque.,
          }
        beneficiaryName: { type: [string, "null"] }
        beneficiaryDocument:
          {
            type: [string, "null"],
            description: CPF ou CNPJ do titular cadastrado.,
          }
        idempotencyKey: { type: string, minLength: 10, maxLength: 200 }
    WithdrawalStatus:
      type: string
      enum:
        [
          processando,
          transferido,
          rejeitado,
          cancelado,
          devolvido,
          expirado,
          falha,
        ]
    WithdrawalCreated:
      type: object
      properties:
        withdrawalId: { type: string, format: uuid }
        status: { $ref: "#/components/schemas/WithdrawalStatus" }
      example:
        {
          withdrawalId: 01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47,
          status: processando,
        }
    Withdrawal:
      type: object
      properties:
        id: { type: string, format: uuid }
        status: { $ref: "#/components/schemas/WithdrawalStatus" }
        amount: { type: integer, description: Centavos. }
        currencyCode: { type: string, const: BRL }
        pixKeyType:
          {
            type: string,
            enum: [CPF, CNPJ, EMAIL, PHONE, EVP],
            description: Grafia de resposta; difere da grafia do pedido.,
          }
        pixKey: { type: string, description: Mascarada. }
        createdAt: { type: string, format: date-time }
        pixEndToEndId:
          { type: [string, "null"], description: Preenchido em `transferido`. }
        transferredAt: { type: [string, "null"], format: date-time }
        rejectedAt:
          {
            type: [string, "null"],
            format: date-time,
            description: Preenchido em `rejeitado` e `expirado`.,
          }
        rejectionReason: { type: [string, "null"] }
        rejectionSource:
          {
            type: [string, "null"],
            enum: [operator, merchant, system, bank_return, null],
          }
        failedAt: { type: [string, "null"], format: date-time }
        failureCode:
          {
            type: [string, "null"],
            description: Informativo. Não monte lógica sobre o valor.,
          }
        failureMessage: { type: [string, "null"], description: Informativo. }
      example:
        id: 01932f5e-7c3a-7b21-9f4e-2d8c6a1b0e47
        status: transferido
        amount: 25000
        currencyCode: BRL
        pixKeyType: CNPJ
        pixKey: "************81"
        createdAt: "2026-11-02T14:58:03.412718Z"
        pixEndToEndId: E00000000202611021508Ab12Cd34Ef5
        transferredAt: "2026-11-02T15:08:41.090233Z"
        rejectedAt: null
        rejectionReason: null
        rejectionSource: null
        failedAt: null
        failureCode: null
        failureMessage: null
    WithdrawalList:
      type: object
      properties:
        items:
          { type: array, items: { $ref: "#/components/schemas/Withdrawal" } }
        totalCount: { type: integer }
        page: { type: integer }
        pageSize: { type: integer }
        totalPages: { type: integer }
    WithdrawalCancelled:
      type: object
      properties:
        withdrawalId: { type: string, format: uuid }
        status: { type: string, const: cancelado }
        reversalTransactionId: { type: "null" }
    Balance:
      type: object
      required: [accountId, balances]
      properties:
        accountId: { type: string, format: uuid }
        balances:
          type: array
          items:
            type: object
            required: [balanceType, amount, currencyCode]
            properties:
              balanceType:
                {
                  type: string,
                  description: "`Available`, `Blocked`, `PendingSettlement`, `Reserved` ou `PendingWithdrawal`. Trate valores novos sem erro.",
                  examples: [Available],
                }
              amount: { type: integer, description: Menor unidade da moeda (centavos)., examples: [10550] }
              currencyCode: { type: string, enum: [BRL, USD] }
              updatedAt: { type: [string, "null"], format: date-time }
    Money:
      type: object
      description: Valor inteiro em centavos e moeda.
      properties:
        amount: { type: integer, examples: [183427] }
        currency: { type: string, const: BRL }
    Statement:
      type: object
      properties:
        accountNumber: { type: string }
        merchantId:
          { type: string, format: uuid, description: Recebedor dono da conta. }
        period:
          type: object
          properties:
            from: { type: [string, "null"], format: date-time }
            to: { type: [string, "null"], format: date-time }
        currentBalance: { $ref: "#/components/schemas/Money" }
        scheduled: { $ref: "#/components/schemas/Money" }
        entries:
          type: array
          items:
            type: object
            properties:
              id: { type: string, examples: [stme-AAECAwQFBgc] }
              postedAt:
                {
                  type: string,
                  format: date-time,
                  description: Sempre com fuso -03:00.,
                }
              direction:
                {
                  type: string,
                  enum: [credit, debit],
                  description: Enum aberto.,
                }
              amount: { $ref: "#/components/schemas/Money" }
              description: { type: [string, "null"] }
        hasMore: { type: boolean }
        nextCursor: { type: string, description: Ausente na última página. }
    StatementParty:
      type: object
      properties:
        name: { type: [string, "null"] }
        document:
          { type: [string, "null"], description: Mascarado pela instituição. }
        bankName: { type: [string, "null"] }
        agency: { type: [string, "null"] }
        accountNumber: { type: [string, "null"] }
    StatementEntryDetail:
      type: object
      properties:
        id: { type: string }
        accountNumber: { type: string }
        type:
          { type: string, examples: [pix_cashin], description: Enum aberto. }
        pix:
          type: object
          description: Omitido quando não se aplica.
          properties:
            endToEndId: { type: string }
            payer: { $ref: "#/components/schemas/StatementParty" }
            payee: { $ref: "#/components/schemas/StatementParty" }
    Address:
      type: object
      required: [country]
      description: "`country` é obrigatório. `zipCode` é obrigatório no Brasil (ou sem `country`) e opcional nos demais países."
      properties:
        zipCode:
          {
            type: string,
            description: "Brasil: CEP com 8 dígitos, hífen aceito; `00000000` não é aceito no 3DS. Outros países: código postal real, preservado como enviado, ou omitido/vazio quando o país não usa.",
            examples: ["01310100"],
          }
        country: { type: string, description: ISO 3166 alfa-2., examples: [BR] }
        street: { type: string }
        neighborhood: { type: string }
        city: { type: string }
        state: { type: string }
    Device:
      type: object
      description: Dados do dispositivo do comprador, em cartões. Não é devolvido nas respostas.
      properties:
        ipAddress: { type: string }
        userAgent: { type: string }
        language: { type: string }
        screenWidth: { type: integer }
        screenHeight: { type: integer }
        colorDepth: { type: integer }
        fingerprint: { type: string }
    Customer:
      type: object
      required: [name, email]
      properties:
        name: { type: string }
        email: { type: string, format: email }
        document:
          {
            type: string,
            maxLength: 20,
            description: CPF ou CNPJ sem pontuação. Obrigatório em Pix parcelado; recomendado em cartão.,
          }
        phone:
          {
            type: string,
            description: "Formato internacional, ex.: +5511999999999. Obrigatório em Pix parcelado.",
          }
        address:
          {
            $ref: "#/components/schemas/Address",
            description: Obrigatório em cartão.,
          }
        device: { $ref: "#/components/schemas/Device" }
    CardInput:
      type: object
      description: Dados do cartão, ou `token` no lugar deles. Se os dois vierem, o token vence.
      properties:
        number: { type: string, description: "13 a 19 dígitos, Luhn válido." }
        expMonth: { type: integer, minimum: 1, maximum: 12 }
        expYear: { type: integer, description: Quatro dígitos. }
        cvc:
          {
            type: string,
            pattern: "^[0-9]{3,4}$",
            description: Obrigatório com cartão digitado.,
          }
        holderName: { type: string }
        token:
          {
            type: string,
            pattern: "^(ctok_|tok_)[0-9A-HJKMNP-TV-Z]{26}$",
            description: "`tok_` do SDK de campos (1 hora, uma cobrança) ou `ctok_` de cartão salvo em POST /api/v1/card-tokens.",
          }
    PaymentMethodInput:
      type: object
      required: [type, amount]
      properties:
        type: { type: string, enum: [Pix, CreditCard, DebitCard, Pagaleve] }
        amount:
          { type: integer, description: Centavos. Igual ao total da cobrança. }
        card:
          {
            $ref: "#/components/schemas/CardInput",
            description: Obrigatório em CreditCard e DebitCard; proibido nos demais.,
          }
    CreditCardOptions:
      type: object
      properties:
        installments: { type: integer, minimum: 1, maximum: 24, default: 1 }
        threeDSecure:
          { type: string, enum: [automatic, required], default: automatic }
        threeDs:
          type: object
          properties:
            sessionToken:
              {
                type: string,
                description: session_token de POST /v1/3ds/device-data-sessions.,
              }
            checkoutOrigin:
              {
                type: string,
                format: uri,
                description: "Origem HTTPS do checkout (protocolo, domínio e porta, sem caminho), cadastrada para a conta. Obrigatória quando a conta tem várias origens sem padrão e a venda exige 3DS. Inválida ou não cadastrada: `400 three_ds_checkout_origin_invalid`; ausente quando obrigatória: `400 three_ds_checkout_origin_required`.",
                examples: ["https://checkout.loja.example.com"],
              }
        mccs:
          {
            type: array,
            minItems: 1,
            maxItems: 3,
            items: { type: string, pattern: "^[0-9]{4}$" },
            description: Quando orientado no credenciamento. `0000` não é válido.,
          }
    SplitRule:
      type: object
      required: [recipientId, type, amount]
      properties:
        recipientId: { type: string }
        type: { type: string, enum: [percentage, flat] }
        amount:
          {
            type: number,
            description: "percentage: 0 a 100, até 4 casas. flat: centavos inteiros.",
          }
        options:
          type: object
          properties:
            isPrincipal:
              {
                type: boolean,
                description: Exatamente um principal em crédito.,
              }
            chargeMdr: { type: boolean }
            chargebackLiability: { type: boolean }
    SplitRuleResult:
      allOf:
        - $ref: "#/components/schemas/SplitRule"
        - type: object
          properties:
            currency: { type: string }
            allocatedAmount:
              {
                type: [integer, "null"],
                description: "Valor calculado em centavos. `null` = não informado, não zero.",
              }
    Split:
      type: object
      properties:
        rules:
          {
            type: array,
            minItems: 1,
            items: { $ref: "#/components/schemas/SplitRule" },
          }
    CreatePaymentIntentRequest:
      type: object
      required: [amount, currency, paymentMethods, customer]
      properties:
        amount: { type: integer, description: Total em centavos. }
        currency:
          {
            type: string,
            enum: [BRL, USD],
            description: Pix e Pix parcelado só BRL.,
          }
        paymentMethods:
          {
            type: array,
            minItems: 1,
            maxItems: 1,
            items: { $ref: "#/components/schemas/PaymentMethodInput" },
          }
        paymentMethodOptions:
          type: object
          properties:
            pix:
              type: object
              properties:
                expiresInMinutes: { type: integer, minimum: 1, default: 30 }
            creditCard: { $ref: "#/components/schemas/CreditCardOptions" }
            debitCard: { $ref: "#/components/schemas/CreditCardOptions" }
            pagaleve:
              type: object
              properties:
                approveUrl: { type: string, format: uri, maxLength: 2048 }
                cancelUrl: { type: string, format: uri, maxLength: 2048 }
        customer: { $ref: "#/components/schemas/Customer" }
        shipping:
          type: object
          properties:
            address: { $ref: "#/components/schemas/Address" }
        split: { $ref: "#/components/schemas/Split" }
        postbackUrl:
          {
            type: string,
            format: uri,
            description: URL HTTPS para os webhooks desta cobrança.,
          }
        metadata: { type: object, additionalProperties: { type: string } }
        description: { type: string }
        statementDescriptor:
          {
            type: string,
            maxLength: 22,
            description: Identificação na fatura do cartão.,
          }
    PaymentIntentStatus:
      type: string
      enum:
        [
          Created,
          Processing,
          RequiresAction,
          Succeeded,
          Failed,
          Canceled,
          Refund,
          ChargedBack,
        ]
    PaymentIntent:
      type: object
      properties:
        id: { type: string, format: uuid }
        object: { type: string, const: payment_intent }
        amount: { type: integer }
        currency: { type: string }
        status: { $ref: "#/components/schemas/PaymentIntentStatus" }
        statusDetail:
          {
            type: [string, "null"],
            description: "Lista aberta, ex. PENDING_CHALLENGE, AUTHENTICATION_FAILED.",
          }
        merchantId: { type: string }
        paymentMethods:
          type: array
          items:
            type: object
            properties:
              type: { type: string }
              amount: { type: integer }
              currency: { type: string }
              card:
                type: [object, "null"]
                properties:
                  maskedNumber: { type: string }
                  expMonth: { type: integer }
                  expYear: { type: integer }
                  holderName: { type: [string, "null"] }
                  brand: { type: [string, "null"] }
                  lastFourDigits: { type: string }
                  bin: { type: [string, "null"] }
                  cardTokenUid:
                    {
                      type: string,
                      description: Presente quando cobrado com token.,
                    }
                  threeDs:
                    type: [object, "null"]
                    properties:
                      used: { type: boolean }
                      liabilityShift: { type: [boolean, "null"] }
              pix:
                type: [object, "null"]
                properties:
                  pixKeyType: { type: string }
                  expiresInMinutes: { type: integer }
        customer: { type: object }
        split:
          type: [object, "null"]
          properties:
            rules:
              {
                type: array,
                items: { $ref: "#/components/schemas/SplitRuleResult" },
              }
        metadata: { type: object, additionalProperties: { type: string } }
        lastPaymentError:
          type: [object, "null"]
          properties:
            code: { type: string }
            message: { type: string }
            category:
              type: [string, "null"]
              enum:
                [
                  issuer_decline,
                  card_data_invalid,
                  authentication,
                  contract_validation,
                  merchant_config,
                  acceptance,
                  provider_unavailable,
                  provider_rejected,
                  ambiguous,
                  internal,
                  null,
                ]
            providerErrorCode: { type: [string, "null"] }
        nextAction:
          type: [object, "null"]
          properties:
            type: { type: string, const: IFRAME_CHALLENGE }
            url: { type: string, format: uri }
            authenticationId: { type: string, format: uuid }
            expiresAt: { type: string, format: date-time }
        pixPayment:
          type: [object, "null"]
          properties:
            qrCodeEmv: { type: string }
            qrCodeUrl: { type: string, description: Pode vir vazio. }
            pixCopyPaste: { type: string }
            expiresAt: { type: string, format: date-time }
            endToEndId: { type: string, description: Vazio até a confirmação. }
            status:
              {
                type: string,
                description: Registro da geração; não é o estado do pagamento.,
              }
        pagalevePayment:
          type: [object, "null"]
          properties:
            checkoutId: { type: string }
            checkoutUrl: { type: string, format: uri }
            expiresAt: { type: string, format: date-time }
            status:
              {
                type: string,
                description: Registro do checkout; use o status da raiz.,
              }
        postbackUrl: { type: [string, "null"] }
        description: { type: [string, "null"] }
        statementDescriptor: { type: [string, "null"] }
        created: { type: string, format: date-time }
        succeededAt: { type: [string, "null"], format: date-time }
      example:
        id: 8b727267-bf39-48a4-91ea-eb0780cb48a9
        object: payment_intent
        amount: 1990
        currency: BRL
        status: Processing
        statusDetail: null
        paymentMethods:
          [
            {
              type: Pix,
              amount: 1990,
              currency: BRL,
              card: null,
              pix: { pixKeyType: RANDOM, expiresInMinutes: 30 },
            },
          ]
        split: null
        metadata: { orderId: pedido-pix-1001 }
        lastPaymentError: null
        nextAction: null
        pixPayment:
          {
            qrCodeEmv: "00020126...",
            qrCodeUrl: "",
            pixCopyPaste: "00020126...",
            expiresAt: "2026-09-08T15:30:00Z",
            endToEndId: "",
            status: PENDING,
          }
        pagalevePayment: null
        created: "2026-09-08T15:00:00Z"
        succeededAt: null
    RefundRequest:
      type: object
      properties:
        reason: { type: string }
        idempotencyKey: { type: string }
    RefundAccepted:
      type: object
      properties:
        refundId: { type: string, examples: [rfnd_EXEMPLO] }
        status: { type: string, enum: [requested, processing, pending_review] }
    PagalevePreAnalysisRequest:
      type: object
      required: [amount, customer]
      properties:
        amount:
          {
            type: integer,
            minimum: 1,
            maximum: 1000000,
            description: Centavos de BRL.,
          }
        customer:
          type: object
          description: Pelo menos um identificador. Recomendado enviar os três.
          properties:
            document: { type: string }
            email: { type: string }
            phone: { type: string }
    WebhookPaymentIntentData:
      type: object
      properties:
        id: { type: string, format: uuid }
        object: { type: string, const: payment_intent }
        amount: { type: integer }
        currency: { type: string }
        status:
          {
            type: string,
            description: "Succeeded/Failed em PascalCase; refunded, refund_failed, charged_back em minúsculas.",
          }
        merchantId: { type: string }
        metadata: { type: object, additionalProperties: { type: string } }
        refundId: { type: string }
    WebhookEvent:
      type: object
      required: [eventType]
      properties:
        eventType:
          type: string
          enum:
            [
              payment_intent.succeeded,
              payment_intent.failed,
              payment_intent.refunded,
              payment_intent.refund_failed,
              payment_intent.charged_back,
            ]
        data: { $ref: "#/components/schemas/WebhookPaymentIntentData" }
