Pular para o conteúdo

Erros & Idempotência

Toda operação financeira (criar cobrança PIX, emitir boleto, criar refund) exige o header Idempotency-Key — um valor único (recomendado: um UUID) que você gera por operação. Ele garante que repetir a mesma requisição (por timeout, retry de rede etc.) não cria um segundo recurso.

Idempotency-Key: 3f2a9c1e-8b7d-4e6f-9a1b-2c3d4e5f6a7b
Cenário Resultado
1ª chamada 201 Created — recurso novo.
Replay: mesma key + mesmo corpo 200 OK — devolve o recurso original, sem criar outro.
Conflito: mesma key + corpo diferente 409 IDEMPOTENCY_KEY_REUSED.
Key ausente 400 IDEMPOTENCY_KEY_REQUIRED.

Exceto o POST /v1/oauth/token (que segue a RFC 6749), todos os erros da API usam o mesmo envelope:

{
"error": {
"code": "CHARGE_NOT_FOUND",
"message": "Cobrança não encontrada.",
"request_id": "req_123",
"documentation_url": "https://docs.vmixpay.com/errors/CHARGE_NOT_FOUND"
}
}
  • code — código de erro estável e legível por máquina (faça branch nele, não na message).
  • message — descrição humana (pode mudar; não dependa do texto).
  • request_id — identificador da requisição; inclua-o ao reportar um problema.
  • documentation_url — link para detalhes do código.
HTTP Significado Exemplos de code
400 Requisição inválida IDEMPOTENCY_KEY_REQUIRED, AMOUNT_INVALID, INVALID_DUE_DATE, CURSOR_INVALID, LIMIT_INVALID
401 Não autenticado Bearer ausente/inválido/expirado
403 Sem permissão Scope faltando, ACCOUNT_MISMATCH, KYC_REQUIRED
404 Não encontrado CHARGE_NOT_FOUND, REFUND_NOT_FOUND, WALLET_ACCOUNT_NOT_FOUND
409 Conflito de estado IDEMPOTENCY_KEY_REUSED, CHARGE_NOT_PENDING, CHARGE_NOT_REFUNDABLE
422 Não processável REFUND_EXCEEDS, MERCHANT_ADDRESS_REQUIRED
429 Rate limit excedido
502 Falha no banco (re-tentável) BANK_UNAVAILABLE

O endpoint de token não usa o envelope acima. Ele segue a RFC 6749 §5.2:

{ "error": "invalid_client" }

Os valores possíveis são unsupported_grant_type (400) e invalid_client (401). Veja Autenticação.

Status Significado
pending Criada, aguardando pagamento.
paid Paga.
expired Vencida sem pagamento.
cancelled Cancelada.
refunded Totalmente devolvida.
failed Falhou.

As listas (charges, payments, events, ledger) usam cursor pagination (keyset), não offset. A resposta é sempre { data, next_cursor }:

  • Repasse o next_cursor (opaco, base64url) em ?cursor=<valor> para a próxima página. Quando vier null, acabou.
  • limit controla o tamanho da página: default 25, máximo 100 (valores acima são reduzidos a 100).
  • Pagine sempre seguindo o next_cursor — não monte cursores à mão.

Erros: 400 CURSOR_INVALID (cursor malformado) · 400 LIMIT_INVALID (limit não-inteiro ou ≤ 0).

O saldo e o extrato são por conta (account_id), com o scope payments:read:

  • GET /v1/accounts/{account_id}/balance{ "balance": "194.50", "currency": "BRL" }.
  • GET /v1/accounts/{account_id}/ledger → extrato da wallet com cursor pagination.

O balance/amount trafegam como string em reais. Uma conta própria sem movimento retorna balance: "0.00" (não 404); uma conta inexistente ou de outro merchant retorna 404 WALLET_ACCOUNT_NOT_FOUND (tenant-safe).