> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usevext.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Estornar cobrança

> Devolve o dinheiro ao comprador, no todo ou em parte.

<Note>
  Os valores aqui são **inteiros em centavos**: R\$ 100,00 se envia como `10000`. Decimais são
  recusados com `422`, e o porquê está em [Valores em centavos](/essenciais/valores-em-centavos).
</Note>

<Note>
  Envie `Idempotency-Key` nesta chamada e você pode repetir com tranquilidade depois de um erro de
  rede: a mesma chave devolve o recurso que já existe, em vez de criar um segundo.
  Veja [Idempotência](/essenciais/idempotencia).
</Note>

<Warning>
  Sem `amount`, devolvemos **o que ainda cabe estornar** — e não o valor da venda. Numa cobrança já
  devolvida pela metade, mandar o valor cheio de volta tiraria o dinheiro duas vezes do vendedor.
</Warning>

`amount`, `fee_returned` e `seller_debit` respondem perguntas diferentes e por isso vêm separados.
A aritmética está em [Estornos](/essenciais/estornos).


## OpenAPI

````yaml api-reference/openapi.yaml POST /v1/charges/{code}/refund
openapi: 3.1.0
info:
  title: Vext - API de pagamentos PIX com split
  version: 1.0.0
  summary: Cobranças PIX, estornos e saldo do vendedor.
  description: |
    API pública do gateway. Todo endpoint desta versão vive sob `/v1`.

    Quatro regras valem para toda chamada. Cada uma tem uma página própria
    nos guias, com exemplos nas quatro linguagens.

    - **Dinheiro é sempre centavos inteiros.** R$ 100,00 se envia como
      `10000`; um decimal devolve `422`. Ver
      [Valores em centavos](/essenciais/valores-em-centavos).
    - **A chave secreta vai no header e fica no servidor.** Cada chave
      carrega escopos; fora do escopo devolve `403`, e não `401`. Ver
      [Autenticação](/essenciais/autenticacao).
    - **Envie `Idempotency-Key` em todo `POST`.** Repetir com a mesma chave
      devolve a resposta gravada em vez de criar um segundo recurso. Ver
      [Idempotência](/essenciais/idempotencia).
    - **Trate os erros pelo `code`**, nunca pela `message` - o código é
      contrato, o texto pode ser reescrito a qualquer momento. Ver
      [Erros](/essenciais/erros).
  contact:
    name: Suporte Vext
    email: suporte@usevext.com
servers:
  - url: https://api.usevext.com
    description: Produção
security:
  - apiKey: []
tags:
  - name: Cobranças
    description: Criação, consulta e estorno de cobranças PIX.
  - name: Saldo
    description: Quanto o vendedor tem e quanto pode sacar.
paths:
  /v1/charges/{code}/refund:
    post:
      tags:
        - Cobranças
      summary: Estornar cobrança
      description: |
        Devolve o dinheiro ao comprador, no todo ou em parte.

        Sem `amount`, devolve **o que ainda cabe estornar** - e não o valor
        da venda. Numa cobrança já devolvida pela metade, mandar o valor
        cheio de volta tiraria o dinheiro duas vezes do vendedor.

        O estorno **não** é barrado por falta de saldo: o dinheiro é do
        comprador e precisa voltar. Se o vendedor já sacou, o saldo dele
        fica negativo e as vendas seguintes cobrem automaticamente.

        `amount`, `fee_returned` e `seller_debit` respondem coisas
        diferentes e por isso vêm separados - é o que evita a pergunta "por
        que saíram R$ 93,01 se eu estornei R$ 100,00?".
      operationId: refundCharge
      parameters:
        - $ref: '#/components/parameters/ChargeCode'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundChargeRequest'
            examples:
              integral:
                summary: Estorno do que resta
                value:
                  reason: Comprador desistiu
              parcial:
                summary: Estorno parcial de R$ 40,00
                value:
                  amount: 4000
                  reason: Devolução de um item
      responses:
        '201':
          description: Estorno registrado e enviado ao provedor.
          headers:
            Idempotent-Replayed:
              $ref: '#/components/headers/IdempotentReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refund'
              examples:
                estornado:
                  summary: Estorno integral de R$ 100,00
                  description: |
                    O comprador recebe `amount`. Do saldo do vendedor sai
                    `seller_debit`; `fee_returned` é a parte da nossa taxa
                    que devolvemos. Sempre
                    `amount = seller_debit + fee_returned`.
                  value:
                    object: refund
                    id: rf_01k1y7c3n8b5w2q9r4t6y8u1i3
                    charge: ch_01k1y6r6m6q2x0p3d9v4t7c8n2
                    status: succeeded
                    amount: 10000
                    currency: BRL
                    fee_returned: 699
                    seller_debit: 9301
                    reason: Comprador desistiu
                    failed_reason: null
                    created_at: '2026-08-05T10:12:00+00:00'
                    processed_at: '2026-08-05T10:12:02+00:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '422':
          $ref: '#/components/responses/UnprocessableRefund'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/ProviderUnavailable'
      security:
        - apiKey:
            - refunds:write
components:
  parameters:
    ChargeCode:
      name: code
      in: path
      required: true
      description: Código público da cobrança, no formato `ch_…`.
      schema:
        type: string
        pattern: ^ch_[0-9a-z]{26}$
      example: ch_01k1y6r6m6q2x0p3d9v4t7c8n2
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Identificador único da SUA tentativa. Use o mesmo valor ao repetir
        a requisição depois de um erro de rede.

        Opcional, mas recomendado em toda criação de cobrança e estorno.
        Sem ele, uma repetição cria uma segunda cobrança.
      schema:
        type: string
        maxLength: 255
      example: pedido-1042
  schemas:
    RefundChargeRequest:
      type: object
      properties:
        amount:
          allOf:
            - $ref: '#/components/schemas/Cents'
          minimum: 1
          description: |
            Quanto devolver, em **centavos inteiros**. Omita para devolver
            todo o saldo estornável da cobrança.
        reason:
          type:
            - string
            - 'null'
          maxLength: 255
          description: Motivo, para o seu histórico e o nosso.
    Refund:
      type: object
      required:
        - object
        - id
        - status
        - amount
        - currency
      properties:
        object:
          type: string
          const: refund
        id:
          type: string
          example: rf_01k1y7c3n8b5w2q9r4t6y8u1i3
        charge:
          type: string
          description: Código da cobrança estornada.
        status:
          $ref: '#/components/schemas/RefundStatus'
        amount:
          allOf:
            - $ref: '#/components/schemas/Cents'
          description: |
            O que volta ao comprador, em centavos inteiros. Do ponto de
            vista dele o estorno é sempre integral: a política de taxa
            decide quem financia, nunca quanto ele recebe.
        currency:
          type: string
          const: BRL
        fee_returned:
          allOf:
            - $ref: '#/components/schemas/Cents'
          description: |
            Quanto da nossa taxa devolvemos, em centavos inteiros.
        seller_debit:
          allOf:
            - $ref: '#/components/schemas/Cents'
          description: |
            O que efetivamente sai do saldo do vendedor, em centavos
            inteiros. Sempre `amount - fee_returned`. Pode ser MAIOR que o
            líquido recebido na venda, quando a política retém o custo que o
            provedor não devolve.
        reason:
          type:
            - string
            - 'null'
        failed_reason:
          type:
            - string
            - 'null'
          description: Preenchido quando `status` é `failed`.
        created_at:
          type: string
          format: date-time
        processed_at:
          type:
            - string
            - 'null'
          format: date-time
    Cents:
      type: integer
      format: int64
      description: |
        Valor monetário em **centavos inteiros** de real. R$ 10,00 = `1000`.
        Nunca decimal, nunca string.
    RefundStatus:
      type: string
      enum:
        - pending
        - succeeded
        - failed
      description: |
        `failed` significa que a devolução **não** aconteceu e o saldo do
        vendedor foi reposto. A linha permanece de propósito: uma tentativa
        de devolver dinheiro que não deu certo é exatamente o que alguém vai
        procurar depois.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
            - details
          properties:
            type:
              type: string
              description: |
                Família do erro. Responde "de quem é o problema e o que
                fazer": `invalid_request_error` se corrige mudando a
                chamada, `authentication_error` trocando a chave, e
                `api_error` é nosso - repita, não reescreva.
              enum:
                - authentication_error
                - permission_error
                - invalid_request_error
                - idempotency_error
                - not_found_error
                - rate_limit_error
                - api_error
            code:
              type: string
              description: |
                Código estável do erro. **Trate por ele.** A `message` pode
                ser reescrita a qualquer momento; o código, não.
            message:
              type: string
              description: Explicação em português, para leitura humana.
            details:
              type: object
              description: |
                Sempre objeto, mesmo vazio. Em `validation_failed`, traz os
                campos recusados e suas mensagens.
              additionalProperties: true
  headers:
    IdempotentReplayed:
      description: |
        Presente com valor `true` quando o corpo devolvido é uma resposta
        **gravada**, e não um recurso novo. Nada foi criado nesta chamada.
      schema:
        type: string
        enum:
          - 'true'
  responses:
    Unauthorized:
      description: |
        Chave ausente, desconhecida, revogada ou expirada. A revogada é
        distinguida da inexistente na MENSAGEM, nunca no status: quem
        integra precisa saber que a chave existiu e foi desligada, e quem
        está adivinhando tokens não aprende nada com isso.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            ausente:
              summary: missing_api_key
              value:
                error:
                  type: authentication_error
                  code: missing_api_key
                  message: 'Envie a chave em Authorization: Bearer sk_live_...'
                  details: {}
            invalida:
              summary: invalid_api_key
              value:
                error:
                  type: authentication_error
                  code: invalid_api_key
                  message: Chave de API inválida.
                  details: {}
            revogada:
              summary: revoked_api_key
              value:
                error:
                  type: authentication_error
                  code: revoked_api_key
                  message: >-
                    Esta chave de API foi revogada. Gere uma nova em
                    Desenvolvedores.
                  details: {}
            expirada:
              summary: expired_api_key
              value:
                error:
                  type: authentication_error
                  code: expired_api_key
                  message: Esta chave de API expirou. Gere uma nova em Desenvolvedores.
                  details: {}
    Forbidden:
      description: |
        A chave é válida, mas não tem o escopo do endpoint. `403` e não
        `401` de propósito: `401` diria "sua chave está errada" a quem tem
        a chave certa.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            escopo:
              summary: insufficient_scope
              value:
                error:
                  type: permission_error
                  code: insufficient_scope
                  message: Esta chave não tem o escopo [refunds:write].
                  details:
                    required_ability: refunds:write
                    abilities:
                      - charges:read
                      - charges:write
    NotFound:
      description: |
        O recurso não existe - ou é de outra conta, o que daqui dá no
        mesmo. Não distinguimos os dois casos: responder "existe, mas não é
        seu" confirmaria a existência de cobranças alheias a quem tentasse
        adivinhar códigos.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            naoEncontrado:
              value:
                error:
                  type: not_found_error
                  code: resource_not_found
                  message: Recurso não encontrado.
                  details: {}
    IdempotencyConflict:
      description: Conflito de chave de idempotência.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            reusada:
              summary: idempotency_key_reused
              description: |
                A chave já foi usada com outro corpo. Devolver a resposta
                antiga faria você acreditar que cobrou o valor novo.
              value:
                error:
                  type: idempotency_error
                  code: idempotency_key_reused
                  message: >-
                    Esta chave de idempotência já foi usada com um corpo
                    diferente. Use uma chave nova.
                  details: {}
            emAndamento:
              summary: idempotency_key_in_progress
              value:
                error:
                  type: idempotency_error
                  code: idempotency_key_in_progress
                  message: >-
                    Uma requisição com esta chave ainda está em processamento.
                    Tente novamente em instantes.
                  details: {}
    UnprocessableRefund:
      description: O estorno foi recusado por uma regra de negócio.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            naoEstornavel:
              summary: charge_not_refundable
              value:
                error:
                  type: invalid_request_error
                  code: charge_not_refundable
                  message: Uma venda aguardando pagamento não pode ser estornada.
                  details: {}
            acimaDoRestante:
              summary: exceeds_remaining
              value:
                error:
                  type: invalid_request_error
                  code: exceeds_remaining
                  message: >-
                    O valor pedido (R$ 200,00) é maior que o disponível para
                    estorno (R$ 60,00).
                  details: {}
            prazo:
              summary: refund_window_closed
              description: |
                O prazo conta do PAGAMENTO, não da criação: uma cobrança que
                ficou dias pendente antes de ser paga não chega com o prazo
                já gasto.
              value:
                error:
                  type: invalid_request_error
                  code: refund_window_closed
                  message: O prazo de 90 dias para estornar esta venda já passou.
                  details: {}
            desabilitado:
              summary: refund_disabled
              value:
                error:
                  type: invalid_request_error
                  code: refund_disabled
                  message: >-
                    Estorno não está habilitado para esta conta. Fale com o
                    suporte.
                  details: {}
    RateLimited:
      description: |
        Requisições demais.

        Há três limites, por minuto: um por IP antes da autenticação, e
        depois um de leitura e um de escrita, ambos por CHAVE - o excesso
        de uma integração não consome a cota das outras do mesmo
        vendedor. A escrita é bem mais apertada porque cada cobrança
        criada vira uma chamada ao provedor.

        `Retry-After` diz em quantos segundos voltar. Os cabeçalhos
        `X-RateLimit-*` vêm em TODA resposta, não só nesta: são eles que
        permitem desacelerar antes de bater no limite.
      headers:
        Retry-After:
          description: Segundos até a cota ser reposta.
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Requisições permitidas por minuto neste balde.
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Quantas ainda cabem no minuto corrente.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            cota:
              summary: too_many_requests
              value:
                error:
                  type: rate_limit_error
                  code: too_many_requests
                  message: Muitas requisições. Tente novamente em 42 segundos.
                  details:
                    retry_after: 42
            provedor:
              summary: provider_rate_limited
              value:
                error:
                  type: rate_limit_error
                  code: provider_rate_limited
                  message: >-
                    Limite de requisições do provedor atingido. Tente novamente
                    em instantes.
                  details: {}
    InternalError:
      description: |
        Erro inesperado do nosso lado. Repita com a mesma
        `Idempotency-Key` - ela é o que torna repetir seguro.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            interno:
              value:
                error:
                  type: api_error
                  code: internal_error
                  message: >-
                    Erro interno. Se persistir, informe o horário da chamada ao
                    suporte.
                  details: {}
    ProviderUnavailable:
      description: |
        O provedor de pagamento não respondeu. **Repita com a mesma
        `Idempotency-Key`**: pode ser que a cobrança tenha sido criada lá e
        a resposta se perdido no caminho, e a mesma chave devolve a
        cobrança em vez de criar outra.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            indisponivel:
              value:
                error:
                  type: api_error
                  code: provider_unavailable
                  message: >-
                    O provedor de pagamento não respondeu. Repita a requisição
                    com a mesma Idempotency-Key.
                  details: {}
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: sk_live_… | sk_test_…
      description: |
        Chave secreta do vendedor, criada no painel em **Desenvolvedores**.

        O valor em claro existe uma única vez, na criação. Guardamos só o
        hash SHA-256 - nem o suporte consegue recuperá-lo, o que é o ponto:
        um dump do nosso banco não permite cobrar em nome de ninguém.

        A chave vai no **servidor**. Colocá-la no navegador do comprador a
        entrega a qualquer pessoa que abra a página.

````