# API de APXBCO
Este arquivo descreve a API de APXBCO para uso por assistentes de
programação. Contém tudo que é preciso para integrar: autenticação,
endpoints, webhooks e os erros possíveis.
URL base: https://bank.apxbco.com.br/api
## Como usar este documento
Você é um assistente ajudando alguém a integrar pagamentos Pix. Leia
tudo antes de escrever código. Os pontos marcados com ATENÇÃO são
erros que quebram a integração em produção — respeite-os literalmente.
---
## 1. Autenticação
Toda chamada leva a chave no header Authorization:
Authorization: Bearer sk_live_xxxxx
A chave identifica a conta. Não existe parâmetro de conta em nenhuma
rota, e uma chave nunca alcança dados de outra.
ATENÇÃO: existem TRÊS prefixos. `sk_sbx_` é a chave de SANDBOX
(homologação): mesmos endpoints, dinheiro de mentira, nenhuma provedora
— veja a seção 2.1. Já `sk_test_` e `sk_live_` operam sobre a conta
REAL e movem dinheiro DE VERDADE: para esses dois, o prefixo é só um
rótulo para organizar as chaves. Para testar sem risco, use chave
`sk_sbx_`. Trate qualquer chave `sk_test_`/`sk_live_` como capaz de
mover o dinheiro real da conta.
ATENÇÃO: a chave é uma senha. Guarde no servidor. Nunca no código do
navegador, nunca no aplicativo, nunca em repositório. Quem tem a chave
move o dinheiro da conta.
Tentativas de autenticação com chave inválida são limitadas por IP de
origem. O limite não muda a resposta: você sempre recebe 401
`invalid_key`, nunca um código diferente — não há nada a tratar de
forma especial no seu código por causa disso.
### Restringindo por IP
Toda chave pode ter uma lista de IPs autorizados, configurada no
painel em Credenciais. Uma chave sem lista (o padrão de toda chave já
emitida) continua sem restrição de IP nenhuma. Com a lista
preenchida, chamadas de IP fora dela recebem 401 `invalid_key` — a
mesma resposta de chave errada, para não revelar a existência da
restrição a quem está tentando descobrir o motivo da recusa.
### Escopos
Cada chave carrega apenas os escopos que recebeu na criação:
- `balance:read` — consultar saldo
- `transactions:read` — listar movimentações e ver comprovante
- `transfers:write` — transferir entre contas da plataforma
- `pix:write` — criar cobrança Pix
- `pix:read` — consultar cobrança Pix
- `pix:send` — enviar Pix para uma chave externa e pagar Pix por
QR Code / copia e cola (saída de dinheiro)
- `boleto:write` — criar e consultar cobrança por boleto
- `card:write` — criar e consultar cobrança por cartão de crédito (em produção, com o `cardToken` gerado no navegador do pagador)
- `boleto:pay` — PAGAR boleto de terceiros com o saldo da conta (saída de dinheiro)
- `split:read` — ver a regra de split da conta e o que está livre
- `split:write` — configurar para onde o split da conta é dividido (conta e chave Pix)
- `payroll:read` — consultar funcionários, folhas, histórico e comprovantes da folha
- `payroll:write` — folha de pagamento: cadastrar funcionário, criar/editar/cancelar folha e MANDAR PAGAR
(sai dinheiro da conta; ver a seção da folha)
Chamar uma rota sem o escopo devolve 403 `insufficient_scope`.
Para um cardápio ou loja que só precisa cobrar e confirmar, os dois
escopos suficientes são `pix:write` e `pix:read`.
---
## 2. Primeira chamada: confirme a chave
Antes de qualquer coisa, verifique se a chave responde:
curl https://bank.apxbco.com.br/api/v1/me \
-H "Authorization: Bearer sk_test_sua_chave"
Resposta:
{
"environment": "test",
"scopes": ["pix:write", "pix:read"],
"account": { "id": "e3fe4b3f-..." },
"tenant": { "slug": "paguemais" },
"permissions": { "balance": true, "transactions": true, "transfers": true }
}
---
## 2.1 Sandbox e homologação
O sandbox é a MESMA API atendida por um simulador: dinheiro de mentira,
nenhuma provedora, pagamentos que você dispara. Serve para homologar a
integração inteira sem mover um centavo.
Como funciona:
- Crie uma chave com ambiente "Sandbox" (prefixo `sk_sbx_`), no app em
Credenciais. Use a MESMA URL base e os MESMOS endpoints — o servidor
reconhece o prefixo e responde pelo simulador. Só a chave muda entre
homologação e produção.
- ATENÇÃO: a API real recusa chave `sk_sbx_` (401) e o simulador recusa
chave real nas rotas `/v1/sandbox/...` (403). Uma não alcança a outra.
- Webhooks: cadastre um destino marcado como "Destino de sandbox". Eventos
de sandbox SÓ chegam a destinos de sandbox, e eventos reais nunca chegam
a eles — um pagamento de teste nunca libera um pedido de verdade.
- Cada conta nasce com R$ 10.000,00 de mentira. O Pix enviado cobra tarifa
fixa de R$ 1,00 (para o total ser diferente do valor); o resto não cobra.
- Os dados ficam 30 dias e são apagados.
Endpoints do sandbox (os mesmos da produção, com os mesmos corpos e erros):
GET /v1/me, GET /v1/balance, GET /v1/transactions, GET /v1/transactions/{id},
POST /v1/pix/charges, GET /v1/pix/charges/{id}, POST /v1/boleto/charges,
GET /v1/boleto/charges/{id}, POST /v1/card/charges, GET /v1/card/charges/{id},
POST /v1/transfers, POST /v1/pix/payouts, POST /v1/pix/qr/quote,
POST /v1/pix/qr/pay, GET /v1/card/tokenizer (token de mentira, ver acima). Não simula: pagar boleto (/v1/boleto/quote, /pay, /payments), /v1/payment-links, /v1/split, QR Pix
dinâmico, prazo de recebimento e tabela de tarifas do white-label.
Comandos que só existem no sandbox (exigem chave `sk_sbx_`; envie um
corpo, mesmo vazio: `-d '{}'`):
- `POST /v1/sandbox/charges/{id}/pay` — confirma uma cobrança Pix, boleto
ou cartão em análise → evento `charge.paid`.
- `POST /v1/sandbox/charges/{id}/expire` — vence a cobrança →
`charge.expired`.
- `POST /v1/sandbox/payouts/{id}/complete` — conclui um envio de Pix agora →
`pix.payout.completed`.
- `POST /v1/sandbox/payouts/{id}/fail` — falha o envio e devolve valor e
tarifa → `pix.payout.failed`.
- `POST /v1/sandbox/balance/topup` com `{"amount":"500.00"}` — soma saldo
de mentira.
- `GET /v1/sandbox/test-data` — os valores de teste, em JSON.
Valores de teste:
- Cartões: `4111111111111111` aprovado na hora (`status: "paid"`);
`4000000000000002` recusado (400 `provider_rejected`);
`4000000000009995` fica em análise (`awaiting_risk_analysis`) até você
chamar `/pay` ou `/expire`.
- Chaves Pix (tipo EMAIL): `falha@sandbox.test` → envio aceito que FALHA em
~4 s (`pix.payout.failed`, valor e tarifa voltam);
`recusa@sandbox.test` → recusado na hora (400 `provider_rejected`, nada
debitado). Qualquer outra chave válida → envio aceito que conclui sozinho
em ~4 s (`pix.payout.completed`).
- Pix enviado devolve `status: "processing"`; acompanhe por webhook ou por
`GET /v1/transactions/{id}` (`PENDING` → `SETTLED`/`FAILED`).
- O copia e cola de uma cobrança Pix do sandbox pode ser pago por
`POST /v1/pix/qr/pay`: a cobrança passa a paga (`charge.paid`). QR
dinâmico não é aceito (400 `qr_not_supported`).
- Qualquer CPF/CNPJ válido serve para boleto e cartão; inválido devolve 400
`payer_document_invalid`.
Roteiro de homologação (o que o integrador deve provar antes de ir para
produção):
1. Autenticação: `/v1/me` com chave sandbox; 401 `invalid_key` tratado; 403
`insufficient_scope` tratado.
2. Pix (receber): criar cobrança (201, `status: "open"`, `copyPaste`);
confirmar por webhook `charge.paid` (conferindo a assinatura) E por
consulta `GET /v1/pix/charges/{id}` (`paid`); expirar (`charge.expired`) e
cancelar o pedido; repetir a criação com a mesma `Idempotency-Key`
devolve a mesma cobrança; o saldo sobe.
3. Boleto: emitir com nome e documento (`digitableLine`); documento inválido
(400); pagar (`charge.paid`, `method: "BOLETO"`); expirar.
4. Cartão: aprovado, recusado e em análise — os três tratados.
5. Pix enviado: enviar (201, `processing`, total = valor + tarifa); desfecho
`pix.payout.completed`; falha (`falha@sandbox.test`) com valor e tarifa
devolvidos; recusa (`recusa@sandbox.test`); `insufficient_funds`; sem
`Idempotency-Key` (400 `missing_idempotency_key`); repetir a mesma chave
devolve o mesmo envio e não paga duas vezes.
6. Pix por QR: `/v1/pix/qr/quote`; valor diferente → 400
`qr_amount_changed`; pagar.
7. Webhooks: assinatura HMAC conferida (aviso com assinatura errada é
rejeitado); responder 2xx rápido; o mesmo evento repetido não repete o
efeito; só os eventos assinados.
8. Extrato: paginar `/v1/transactions`; ler o comprovante de uma
movimentação.
9. Produção: só a chave muda (`sk_sbx_` → `sk_live_`, mesma URL); cadastrar
o destino de webhook de produção SEM marcar sandbox; tratar erros pelo
campo `error`, nunca pelo texto; valores como string decimal; uma
transação real de valor baixo antes de divulgar.
A versão interativa deste roteiro, com checklist, está na documentação em
"Homologação e sandbox".
---
## 3. Endpoints
### GET /v1/me
Escopo: nenhum. Confirma a chave e mostra os escopos dela.
### GET /v1/balance
Escopo: `balance:read`
{ "available": "2.99", "held": "0.00", "gross": "2.99", "pending": "0.00", "currency": "BRL" }
`pending` é o que já foi pago em boleto ou cartão e ainda está dentro do
prazo de recebimento do white-label: é do cliente, mas ainda não pode ser
gasto (ver `receivable.released` nos webhooks). Vem `"0.00"` quando o
white-label não usa prazo — o padrão.
### GET /v1/transactions
Escopo: `transactions:read`
Parâmetros: `limit` (padrão 25, máximo 100), `offset` (padrão 0)
{
"items": [
{
"id": "6dbda7b0-...",
"type": "CHARGE_RECEIVED",
"status": "PENDING",
"direction": "IN",
"amount": "49.90",
"fee": "0.00",
"description": "Pedido #1234",
"counterparty": "Maria Silva",
"createdAt": "2026-09-10T15:00:14.993Z",
"settledAt": null
}
],
"total": 128,
"limit": 25,
"offset": 0
}
Pagine com `offset`: ainda há página seguinte enquanto
`offset + items.length < total`.
### GET /v1/transactions/{id}
Escopo: `transactions:read`. Comprovante de uma movimentação.
{
"id": "9a29cbf2-...",
"type": "CHARGE_RECEIVED",
"status": "SETTLED",
"direction": "IN",
"amount": "100.00",
"fee": "15.00",
"total": "85.00",
"description": "Pedido #1234",
"counterparty": { "name": "Maria Silva", "document": null },
"createdAt": "2026-09-10T17:34:30.579Z",
"settledAt": "2026-09-10T17:34:30.601Z",
"correlationId": "57befe60-...",
"feeBreakdown": { "total": "15.00" },
"timeline": [
{ "status": "SETTLED", "source": "PROVIDER_WEBHOOK", "reason": null, "at": "2026-09-10T17:34:30.601Z" }
]
}
- `status`: `PENDING` (em andamento), `SETTLED` (concluída),
`FAILED` (não aconteceu; se saiu dinheiro, ele já voltou),
`CANCELLED` ou `REVERSED`. Para um Pix ou boleto que VOCÊ pagou, é
por aqui que se acompanha o desfecho.
- `total`: o que esta operação moveu de fato na conta — para quem
recebeu, o líquido depois da tarifa; para quem enviou, o valor mais
a tarifa que pagou.
- `fee` / `feeBreakdown.total`: a tarifa desta operação. Só o total
— a composição interna da tarifa não é exposta por esta rota.
- `feeBreakdown` vem `null` quando não há tarifa nesta operação.
- `timeline`: os eventos que levaram ao status atual, do mais antigo
ao mais recente.
### POST /v1/pix/charges
Escopo: `pix:write`. Cobrança avulsa: um QR para UM pagamento.
ATENÇÃO: isto NÃO cria link de pagamento. A cobrança nasce, é paga e
acaba — não tem página pública nem endereço para divulgar. Se o que
você quer é um endereço que várias pessoas possam pagar, use
POST /v1/payment-links, descrito adiante.
Corpo:
{
"amount": "49.90",
"description": "Pedido #1234",
"expiresIn": 3600,
"payer": {
"name": "Maria Silva",
"email": "maria@exemplo.com",
"document": "12345678901"
},
"externalReference": "pedido-1234"
}
- `amount`: string decimal com duas casas. Obrigatório.
- `description`: até 140 caracteres. Opcional — sem ela, o extrato
mostra "Cobrança Pix".
- `expiresIn`: segundos, de 60 a 2592000. Padrão 86400 (24h).
- `payer`: opcional, todos os campos opcionais.
- `externalReference`: seu id do pedido, até 120 caracteres. Volta
na resposta. Opcional, mas use — é como você liga a cobrança ao
pedido no seu sistema.
- `split`: opcional, até 5 linhas. A cobrança já nasce dividida —
quando for paga, cada linha recebe sua fatia automaticamente, sem
chamada extra. Cada linha:
{ "destination": "12345678901", "percentage": "10" }
`destination` é o CPF, CNPJ ou e-mail da conta que recebe (mesmo
tenant). `percentage` é a fatia, como string ("10" = 10%). A soma
das linhas não pode passar de 100% — o que sobra fica com você. A
API resolve cada `destination` na hora: se algum não existir, ou a
soma passar de 100%, a chamada inteira falha (nada é criado) com o
motivo exato. O split roda sobre o valor líquido, depois da sua
própria tarifa — nunca some as taxas na conta de quem recebe.
Split precisa estar habilitado na sua conta. Se não estiver, a
chamada falha com `split_not_enabled` — fale com o suporte para
ativar.
Este campo é AVULSO: vale só para esta cobrança, definido na
criação. Para uma regra que passa a valer automaticamente sobre
tudo que a conta recebe a partir de agora — Pix, boleto pago e
transferência interna recebida, não só cobranças feitas com este
campo — use PUT /v1/split, descrito mais adiante.
Envie `Idempotency-Key` (8 a 128 caracteres `[A-Za-z0-9_.:-]`) para
poder repetir a chamada com segurança: reenviar a mesma chave devolve
a cobrança já criada em vez de gerar outra. Sem a chave, cada chamada
cria uma cobrança nova — é assim que uma tela de checkout sem esse
cuidado costuma duplicar cobrança quando o cliente clica duas vezes ou
a rede treme.
Resposta 201:
{
"id": "e0dfecf5-af2f-40e5-9729-2e25b9a4184a",
"status": "open",
"amount": "49.90",
"description": "Pedido #1234",
"qrCode": "00020101021226900014br.gov.bcb.pix...",
"copyPaste": "00020101021226900014br.gov.bcb.pix...",
"expiresAt": "2026-09-10T15:30:10.457Z",
"externalReference": "pedido-1234"
}
`qrCode` e `copyPaste` são o MESMO valor: o código copia-e-cola.
Para mostrar o QR, gere a imagem a partir dessa string com qualquer
biblioteca de QR code. A API não devolve imagem.
Se você mandou `split` no corpo, a resposta traz o campo `split`
confirmando o que foi resolvido (o id de cada conta destino), para
você conferir antes do pagamento acontecer:
"split": [
{ "destinationSellerId": "7ec3...", "percentage": "10" }
]
### GET /v1/pix/charges/{id}
Escopo: `pix:read`. O estado atual da cobrança.
{
"id": "e0dfecf5-...",
"status": "open",
"amount": "49.90",
"description": "Pedido #1234",
"paidAt": null,
"expiresAt": "2026-09-10T15:30:10.457Z"
}
Estados possíveis:
- `open` — criada, aguardando pagamento
- `paid` — paga e creditada. É o ÚNICO estado que significa dinheiro
na conta
- `expired` — passou da validade sem pagamento
- `cancelled` — cancelada
- `refunded` — estornada
ATENÇÃO: só `paid` libera o pedido. Qualquer outro estado, inclusive
`open`, significa que o dinheiro não entrou.
### POST /v1/boleto/charges
Escopo: `boleto:write`. Cobrança avulsa por boleto: gera a linha
digitável e, quando o provedor devolve, um link para o PDF.
Só funciona quando o provedor configurado no tenant tem a
funcionalidade de boleto habilitada. Se não tiver, a chamada falha —
a mesma cobrança volta a funcionar automaticamente, sem nenhum deploy
extra, assim que um provedor com boleto for configurado.
ATENÇÃO: diferente do Pix, boleto EXIGE nome e documento (CPF ou
CNPJ) de quem paga — o banco emissor recusa sem isso. A confirmação
do pagamento não é instantânea: chega pelo webhook `charge.paid`,
normalmente em até um dia útil depois do pagamento no caixa ou
internet banking de quem pagou.
Corpo:
{
"amount": "49.90",
"description": "Pedido #1234",
"payer": {
"name": "Maria Silva",
"document": "12345678901",
"email": "maria@exemplo.com"
},
"externalReference": "pedido-1234",
"split": [
{ "destination": "anunciante@exemplo.com", "percentage": "10" }
]
}
- `amount`: string decimal com duas casas. Obrigatório.
- `description`: até 140 caracteres. Opcional.
- `payer.name`: 2 a 120 caracteres. Obrigatório.
- `payer.document`: 11 a 20 caracteres. Obrigatório. ATENÇÃO: esta
rota valida só o TAMANHO do documento, não o dígito verificador de
CPF/CNPJ — um documento com formato errado mas comprimento válido
passa na validação da API e só é recusado depois, pelo banco
emissor do boleto. Valide o CPF/CNPJ no seu lado antes de enviar.
- `payer.email`: formato de e-mail, até 200 caracteres. Opcional.
- `externalReference`: até 120 caracteres. Opcional.
- `split`: opcional, até 5 linhas — mesmo formato e mesma regra de
`POST /v1/pix/charges` (soma até 100%, resolvido na hora, aplicado
sozinho quando o boleto for pago).
Envie `Idempotency-Key` (8 a 128 caracteres `[A-Za-z0-9_.:-]`, mesma
regra do Pix) para poder repetir a chamada com segurança: reenviar a
mesma chave devolve o boleto já criado em vez de gerar outro. Não é
obrigatório nesta rota, mas use — sem ele, um retry de rede gera um
segundo boleto para o mesmo pedido.
Resposta 201:
{
"id": "e636775c-...",
"status": "open",
"amount": "49.90",
"description": "Pedido #1234",
"digitableLine": "34191.79001 01043.510047 91020.150008 8 96590000004990",
"bankSlipUrl": "https://provedor.com/boletos/abc123.pdf",
"expiresAt": "2026-09-13T00:00:00.000Z",
"externalReference": "pedido-1234"
}
`bankSlipUrl` pode vir ausente dependendo do provedor — sempre mostre
`digitableLine` como alternativa (o cliente pode digitar ou colar no
internet banking mesmo sem o PDF).
### GET /v1/boleto/charges/{id}
Escopo: `boleto:write`. Mesmo formato de GET /v1/pix/charges/{id}.
Prefira o webhook `charge.paid` para acompanhar o pagamento; a
confirmação de boleto não é instantânea.
ATENÇÃO: `status` nesta rota só assume `open`, `paid`, `expired`,
`cancelled` ou `refunded` — os mesmos cinco estados do Pix. Não existe
um status de "processando" ou "em análise" para boleto: ele fica
`open` até o banco emissor confirmar o pagamento (o que credita e
muda para `paid`) ou até vencer (`expired`).
### Checkout transparente com cartão (o cartão é digitado no SEU site)
Para uma loja online, um sistema de telemedicina, uma assinatura: o pagador digita o cartão na SUA página, sem sair dela, e você cobra pela
API. O número do cartão NUNCA passa pelo seu servidor nem pelo nosso: a operadora (Efí) só aceita um TOKEN gerado no navegador do pagador, com a
biblioteca JavaScript dela. Fluxo:
1. No seu servidor, uma vez: `GET /v1/card/tokenizer` (escopo `card:write`) devolve o `accountId` (identificador público da conta, não é segredo),
o `environment` (`production` ou `sandbox`) e o `script` (a biblioteca da operadora).
2. Na página do pagador, carregue o `script` e gere o token com os dados do cartão:
3. O navegador manda só esse `payment_token` ao SEU servidor, e o seu servidor chama `POST /v1/card/charges` com `cardToken` (no lugar de `card`).
O token vale para UMA cobrança; gerar outro a cada tentativa.
Regras desta conta (Efí): cobrança À VISTA (`installmentCount` 1; parcelado responde `installments_unavailable`) e `payer.email` e `payer.phone` (celular com DDD) são OBRIGATÓRIOS
(`payer_email_required`, `payer_phone_required`). Quem usa cartão cru em `card` numa conta que recebe por token recebe `card_token_required`. No SANDBOX não há biblioteca:
use os tokens de teste `sbx_tok_approved` (aprovado), `sbx_tok_declined` (recusado) e `sbx_tok_analysis` (em análise).
Alternativa sem código de cartão no seu site: `POST /v1/payment-links` com `methods` incluindo `CARD` e redirecionar o pagador ao link — a página
de pagamento hospedada faz a tokenização por você.
### GET /v1/card/tokenizer
Escopo: `card:write`. Devolve o que o seu site precisa para gerar o token (ver "Checkout transparente com cartão"):
{
"provider": "efi",
"accountId": "…identificador público da conta…",
"environment": "production",
"script": "https://cdn.jsdelivr.net/npm/payment-token-efi@3.4.1/dist/payment-token-efi-umd.min.js"
}
Erro 404 `card_tokenizer_unavailable` = esta conta recebe o cartão de outro jeito (cartão cru em `card`) ou ainda não tem cartão habilitado.
### POST /v1/card/charges
Escopo: `card:write`. Cobrança de cartão de crédito
SERVIDOR-A-SERVIDOR: você já tem os dados do cartão em mãos (seu
checkout coletou) e cobra direto — a resposta já diz se foi aprovado,
sem link nem página de checkout no meio.
Em contas que recebem cartão por TOKEN (Efí), envie `cardToken` (gerado no navegador, ver "Checkout transparente com cartão") no lugar de `card`:
mande UM dos dois. Só funciona quando o provedor configurado no tenant tem a
funcionalidade de cartão habilitada. Se não tiver, a chamada falha —
volta a funcionar sozinha assim que um provedor com cartão for
configurado, sem deploy extra.
ATENÇÃO SOBRE OS DADOS DO CARTÃO: número, validade e CVV chegam SÓ
como parâmetro desta chamada — a plataforma nunca grava nem loga
esses campos; eles vão direto ao provedor e saem de escopo assim que
a resposta volta. Coletar e transmitir dado de cartão dessa forma
coloca SUA integração sob escopo de PCI-DSS: use TLS de ponta a ponta
e não grave esses campos do seu lado sem o mesmo cuidado.
Corpo:
{
"amount": "49.90",
"description": "Pedido #1234",
"installmentCount": 1,
"payer": {
"name": "Maria Silva",
"document": "12345678901",
"email": "maria@exemplo.com",
"phone": "11912345678",
"ip": "203.0.113.42"
},
"card": {
"holderName": "MARIA SILVA",
"number": "4111111111111111",
"expiryMonth": "12",
"expiryYear": "2029",
"cvv": "123"
},
"externalReference": "pedido-1234",
"split": [
{ "destination": "anunciante@exemplo.com", "percentage": "10" }
]
}
- `amount`: string decimal com duas casas. Obrigatório.
- `description`: até 140 caracteres. Opcional.
- `externalReference`: até 120 caracteres. Opcional.
- `installmentCount`: opcional, inteiro de 1 a 12.
- `payer.name`: 2 a 120 caracteres. `payer.document`: 11 a 20
caracteres — ATENÇÃO: só o tamanho é validado aqui, não o dígito
verificador; valide CPF/CNPJ no seu lado. `payer.ip`: 3 a 64
caracteres — o IP de quem está pagando, usado na análise de risco do
provedor, envie o IP real do cliente final, nunca o do seu servidor.
- `card.holderName`: 2 a 120 caracteres, como impresso no cartão.
- `card.number`: 12 a 19 dígitos, só números (sem espaço nem traço).
- `card.expiryMonth`: dois dígitos, "01" a "12".
- `card.expiryYear`: quatro dígitos, "AAAA".
- `card.cvv`: 3 ou 4 dígitos.
- `split`: opcional, até 5 linhas — mesmo formato dos demais métodos,
resolvido ANTES de cobrar no cartão (um destino inválido barra a
cobrança inteira, em vez de cobrar e falhar o split depois).
ATENÇÃO: se mais de um campo do corpo for inválido, a resposta traz
APENAS o primeiro erro encontrado, não a lista completa — corrija um,
reenvie, e trate a possibilidade de outro erro aparecer na tentativa
seguinte. Valide o corpo no seu lado antes de chamar a API sempre que
possível, para não depender desse ciclo.
Envie `Idempotency-Key` (8 a 128 caracteres `[A-Za-z0-9_.:-]`) para
poder repetir a chamada com segurança: reenviar a mesma chave devolve
o resultado da cobrança já feita em vez de cobrar o cartão de novo.
Não é obrigatório nesta rota, mas é ALTAMENTE recomendado — mais do
que em Pix ou boleto: sem idempotência, um timeout de rede seguido de
retry pode cobrar o cliente duas vezes no cartão, e diferente de Pix
(que só recebe) isso é uma cobrança indevida real que vocês têm que
estornar na mão depois.
Resposta 201 (aprovado):
{
"id": "e636775c-...",
"status": "paid",
"amount": "49.90",
"cardLast4": "1111",
"cardBrand": "visa",
"externalReference": "pedido-1234"
}
`status` também pode vir `awaiting_risk_analysis` quando o provedor
retém a aprovação para análise manual, em vez de aprovar ou recusar na
hora. Trate como "ainda não sei o resultado", não como sucesso nem
como falha.
ATENÇÃO — importante: `awaiting_risk_analysis` só aparece NESTA
resposta (a do POST, na hora da cobrança). Enquanto a análise está
pendente, a cobrança continua `open` internamente, então
GET /v1/card/charges/{id} devolve `status: "open"`, não
`"awaiting_risk_analysis"` — não é bug seu nem nosso, é assim que o
estado é representado hoje. O desfecho final (aprovado ou recusado)
chega pelo webhook `charge.paid` quando aprovado; se recusado depois
da análise, a cobrança expira e você recebe `charge.expired`. Se
recebeu `awaiting_risk_analysis` no POST, não trate como erro nem
libere o pedido — espere o webhook.
Se o cartão for recusado NA HORA (resposta síncrona, sem passar por
análise), a chamada falha com 400 e `error: "provider_rejected"` —
veja a seção 6 para o texto que diferencia recusa do cartão de erro
técnico do provedor.
**3DS e Device ID (quando a provedora do cartão usa, hoje o Mercado Pago)**
- Campo opcional `deviceId` no corpo do POST: o Device ID do navegador de quem paga.
Carregue `https://www.mercadopago.com/v2/security.js` na SUA página de checkout e envie o valor de `MP_DEVICE_SESSION_ID`
(de 8 a 300 caracteres: letras, números, ponto, hífen e sublinhado). Melhora a aprovação; sem ele a cobrança segue normal.
- O banco do portador pode pedir autenticação (3DS). Nesse caso a resposta vem com `status: "challenge_required"` e `challengeUrl`:
abra essa URL num iframe (ou janela) para o pagador concluir. NÃO libere o pedido e NÃO trate como erro. Quando ele terminar,
o desfecho chega pelo webhook `charge.paid` (aprovado); se o banco recusar ou o prazo (40 minutos) vencer, a cobrança fica
`cancelled`. Confirme sempre com `GET /v1/card/charges/{id}` antes de liberar. Só abra URLs `https://` de domínio
`mercadopago` / `mercadolibre`.
### GET /v1/card/charges/{id}
Escopo: `card:write`. Mesmo formato de GET /v1/pix/charges/{id}.
Como a cobrança de cartão é síncrona na maioria dos casos, a resposta
de POST /v1/card/charges já diz o desfecho — use esta rota para
conciliar depois, não para consultar logo em seguida da cobrança.
Único status possível aqui hoje: `open`, `paid`, `expired`,
`cancelled` ou `refunded` (ver ATENÇÃO acima sobre
`awaiting_risk_analysis` não aparecer aqui). Um cartão recusado na
hora fica `cancelled`.
### Pagar boleto de terceiros (`boleto:pay`)
Pague contas e boletos com o saldo da conta, pela API. Três passos: consultar, pagar, acompanhar. É saída de dinheiro: escopo próprio
`boleto:pay` (separado de `boleto:write`, que EMITE boleto de cobrança) e `Idempotency-Key` obrigatório no pagamento.
#### POST /v1/boleto/quote
Escopo `boleto:pay`. Não move dinheiro. Corpo: `{ "digitableCode": "linha digitável ou código de barras" }`. Resposta: valor de HOJE
(`totalAmount`, com juros e multa), `dueDate`, `originalAmount`, beneficiário (`recipientName`, `recipientDocument` mascarado), `fee` (tarifa) e `total`
(valor + tarifa, o que sai da conta).
#### POST /v1/boleto/pay
Escopo `boleto:pay` + header `Idempotency-Key` (8 a 128 caracteres). Corpo:
{ "digitableCode": "…", "amount": "259.90", "description": "Fornecedor X" }
`amount` é o `totalAmount` que o seu sistema mostrou e o usuário confirmou. O servidor consulta o boleto de novo e RECUSA com `boleto_amount_changed`
(e o valor novo em `totalAmount`) se ele mudou — juros e multa correm de um dia para o outro. Resposta 201:
`{ "id", "status", "amount", "fee", "total", "digitableCode" }`. Repetir a mesma `Idempotency-Key` NÃO paga duas vezes.
#### GET /v1/boleto/payments/{id}
Escopo `boleto:pay`. Estado do pagamento: `pending` (aguardando o banco), `settled` (pago) ou `failed` (o banco não efetivou; o valor volta à conta).
Boleto pago pode levar de minutos até o próximo dia útil para ficar `settled`: não libere nada na sua ponta antes disso.
Erros: `insufficient_balance` (saldo), `invalid_request`, `boleto_amount_changed`, `provider_rejected` ("o boleto não pôde ser consultado ou pago;
nada foi debitado"). No sandbox estas três rotas respondem `boleto_pay_not_in_sandbox` (pagar boleto ainda não é simulado); emitir boleto de cobrança
funciona normalmente.
### POST /v1/pix/payouts
Escopo: `pix:send`. Envia Pix para uma chave externa (Pix de SAÍDA).
Diferente de POST /v1/transfers (que move entre contas DESTE banco):
aqui o dinheiro sai para uma chave Pix de qualquer instituição. Debita a
sua conta.
ATENÇÃO: exige o header `Idempotency-Key` (8 a 128 caracteres de
[A-Za-z0-9_.:-]). Sem ele a resposta é 400 `missing_idempotency_key`.
Reenviar a mesma chave devolve o envio já feito em vez de pagar de novo
— é o que protege contra clique duplo e retry de rede num pagamento.
Corpo:
{
"amount": "150.00",
"pixKey": "maria@exemplo.com",
"pixKeyType": "EMAIL",
"description": "Pagamento fornecedor"
}
- `amount`: string decimal com duas casas. Obrigatório.
- `pixKey`: a chave Pix do destino. Obrigatório.
- `pixKeyType`: um de `CPF`, `CNPJ`, `EMAIL`, `PHONE`, `RANDOM`.
- `description`: até 140 caracteres. Opcional.
Resposta 201:
{
"id": "8f1a...",
"status": "processing",
"amount": "150.00",
"fee": "1.20",
"total": "151.20",
"pixKey": "ma****@exemplo.com",
"providerReference": "E1890...",
"endToEndId": "E1890..."
}
- `amount` é o valor enviado; `fee` a tarifa; `total` o que saiu da
conta (amount + fee). `pixKey` volta mascarada.
- `status` reflete o estado no provedor no momento do envio
(`processing`, `completed`…).
O desfecho final de um Pix de saída chega pelos webhooks
`pix.payout.completed` (enviado) e `pix.payout.failed` (não saiu — o
valor e a tarifa voltam à conta sozinhos). ATENÇÃO: esses dois eventos só
são entregues a quem os assinou PELO NOME ao cadastrar o destino; um
destino sem filtro de eventos não os recebe. O corpo traz `transactionId`
(o mesmo `id` desta resposta), `sellerId`, `amount` e `fee` em CENTAVOS.
O `transfer.completed` continua sendo só das transferências entre contas
da plataforma.
Sem webhook, dá para acompanhar consultando `GET /v1/transactions/{id}`
com o `id` devolvido: o `status` vai de `PENDING` para `SETTLED`
(enviado) ou `FAILED`. Use o webhook como aviso e a consulta como
conferência — se um aviso não chegar, a consulta tem a resposta.
ATENÇÃO: sem saldo suficiente para `amount` + `fee`, a resposta é 400
`insufficient_funds` e nada é enviado. Se a conta não tem Pix de saída
habilitado, é 400 `no_provider`. Se a conta está com as saídas
bloqueadas, é 400 `outbound_blocked`.
### POST /v1/pix/qr/quote
Escopo: `pix:send`. Lê um Pix copia e cola / QR Code de terceiro e diz o
que ele pede, ANTES de mexer em dinheiro. Não move nada.
Só funciona em contas cuja provedora de pagamento sabe pagar QR Code
(hoje a Efí). Nas demais a resposta é 400 `qr_not_supported`.
Corpo:
{
"brCode": "00020101021226830014BR.GOV.BCB.PIX2561qrcodespix.sejaefi.com.br/v2/267e5552...6304CE83",
"amount": "50.00"
}
- `brCode`: o copia e cola completo, exatamente como foi recebido
(30 a 1024 caracteres). Espaços dentro dele são legítimos — não
remova. O CRC do código é conferido: código cortado ou alterado é
recusado.
- `amount`: opcional. Só para QR estático que NÃO define valor: com ele
a tarifa, o total e a conferência de saldo são calculados.
Resposta 200:
{
"kind": "DYNAMIC",
"amount": "137.50",
"amountEditable": false,
"receiverName": "LOJA EXEMPLO LTDA",
"receiverDocument": "**********0190",
"city": "SAO PAULO",
"expiresAt": "2026-10-01T18:30:00.000Z",
"fee": "1.20",
"total": "138.70",
"spendable": "500.00",
"suficiente": true
}
- `kind`: `DYNAMIC` (cobrança com valor e validade, resolvida no
provedor de quem cobra) ou `STATIC` (chave Pix, valor opcional).
- `amount`: `null` em QR estático sem valor — nesse caso
`amountEditable` é `true` e o valor é você quem informa.
- `receiverDocument` vem mascarado. `fee`, `total`, `spendable` e
`suficiente` só vêm quando há valor definido.
Recusas (400): `brcode_invalid`, `brcode_crc`, `brcode_malformed`,
`brcode_currency`, `brcode_unreachable` (não foi possível consultar o
endereço do código), `brcode_url_blocked`, `qr_expired`,
`qr_not_active` (já pago ou cancelado), `qr_amount_editable` (o código
permite o pagador alterar o valor, ainda não suportado),
`qr_not_supported`.
### POST /v1/pix/qr/pay
Escopo: `pix:send`. Paga um Pix copia e cola / QR Code de terceiro,
como qualquer app de banco: "ler o QR e pagar".
ATENÇÃO: exige o header `Idempotency-Key` (8 a 128 caracteres de
[A-Za-z0-9_.:-]); sem ele a resposta é 400 `missing_idempotency_key`.
Reenviar a mesma chave devolve o resultado da primeira chamada e nunca
paga duas vezes.
Corpo:
{
"brCode": "00020101021226830014BR.GOV.BCB.PIX2561qrcodespix.sejaefi.com.br/v2/267e5552...6304CE83",
"amount": "137.50",
"description": "Pagamento fornecedor"
}
- `amount`: o valor que o SEU sistema mostrou ao usuário. O servidor lê
o código de novo e recusa com `qr_amount_changed` se o valor mudou
desde a consulta — ninguém paga um valor diferente do que viu.
Mostre de novo o valor novo e peça outra confirmação.
- `description`: opcional, até 140 caracteres.
Resposta 201:
{
"id": "8f1a...",
"status": "processing",
"amount": "137.50",
"fee": "1.20",
"total": "138.70",
"receiver": "LOJA EXEMPLO LTDA",
"providerReference": "8f1a...",
"endToEndId": "E1890..."
}
- Sai da conta `total` (valor + tarifa), no mesmo momento.
- O desfecho chega pelos webhooks pix.payout.completed / pix.payout.failed (só a quem os assina pelo nome); para conferir, consulte
`GET /v1/transactions/{id}` com o `id` devolvido — `PENDING` →
`SETTLED` (pago) ou `FAILED` (não saiu; valor e tarifa voltam à conta
sozinhos).
ATENÇÃO: se a resposta for 400 `pending_reconciliation`, o pagamento
PODE ter saído (o provedor não respondeu a tempo). NÃO envie de novo com
outra chave: consulte `GET /v1/transactions/{id}` ou repita a MESMA
`Idempotency-Key`.
Erros (400, `error` estável): os de `quote` mais `qr_amount_changed`,
`amount_required`, `insufficient_funds`, `limit_exceeded`,
`daily_limit_exceeded`, `outbound_blocked`, `no_provider`,
`provider_rejected` (o provedor recusou: nada foi debitado) e
`pending_reconciliation`.
### GET /v1/split
Escopo: `split:read`. Estado atual do split da conta.
Mostra quanto o administrador do white-label já reservou (só o
percentual — o `destination` daquela fatia NUNCA é revelado à conta,
mesmo por API) e quanto sobra livre para a própria conta configurar,
além das regras que a própria conta já tem.
Resposta 200:
{
"adminConfigured": false,
"adminPercentage": "0.000000",
"availablePercentage": "80.000000",
"availableByOperation": { "PIX": "80.000000", "TRANSFER": "100.000000", "BOLETO": "100.000000", "CARD": "100.000000", "LINK": "100.000000" },
"adminByOperation": { "PIX": "0.000000", "TRANSFER": "0.000000", "BOLETO": "0.000000", "CARD": "0.000000", "LINK": "0.000000" },
"rules": [
{ "id": "1e04...", "destination": "Maria Fornecedora", "percentage": "10.000000", "operations": ["PIX"] }
],
"pixRules": [
{ "id": "9c2b...", "label": "Fornecedor Pix", "keyType": "CNPJ", "keyMasked": "**********0138",
"percentage": "10.000000", "feePayer": "SHARED" }
],
"pix": { "enabled": true, "ready": true, "randomKey": true, "minimum": "1.00" }
}
- `pixRules`: as linhas de split para CHAVE PIX desta conta (a chave nunca vem inteira, só `keyMasked`).
- `pix.enabled`: o white-label liberou o split para chave Pix. `pix.ready`: o Pix de saída dele está configurado.
`pix.minimum`: menor repasse aceito; fatia menor que isso fica com a conta.
- `adminConfigured`: true se o administrador do white-label travou
algum percentual nesta conta.
- `availablePercentage`: o que ainda está livre em TODAS as entradas
(o menor valor de `availableByOperation`).
- `availableByOperation` / `adminByOperation`: por tipo de entrada, quanto ainda
pode ser usado pela própria conta (100% menos o que o admin reservou, menos o que a
conta já configurou) e quanto o admin reservou. Os tipos de entrada são `PIX`
(Pix recebido), `TRANSFER` (transferência interna), `BOLETO`, `CARD` (cartão) e
`LINK` (qualquer pagamento feito por link de pagamento, seja Pix, boleto ou cartão).
- `operations` (em `rules` e `pixRules`): em quais entradas a linha vale; `null` = todas.
- `rules`: as linhas que a PRÓPRIA conta configurou (nunca as do
admin).
### PUT /v1/split
Escopo: `split:write`. Configura a regra PERMANENTE de split da
conta.
Diferente do campo `split` de POST /v1/pix/charges (que vale só para
UMA cobrança): esta regra passa a valer automaticamente sobre TUDO que
a conta recebe dali em diante — Pix, boleto pago e transferência
interna recebida — sem precisar mandar nada em cada chamada.
Corpo:
{
"lines": [
{ "destination": "maria@exemplo.com", "percentage": "10" }
]
}
ATENÇÃO: substitui a lista INTEIRA, não soma à anterior. Mandar
`"lines": []` remove todas as regras da própria conta (sem mexer no
que o administrador do white-label configurou, que é um nível
separado e não pode ser alterado por esta rota).
`destination` é CPF, CNPJ ou e-mail (mesmo tenant); `percentage` é a
fatia como string ("10" = 10%). A soma das linhas mais o que o
administrador já reservou nunca pode passar de 100%. Cada
`destination` é resolvido antes de salvar — se algum não existir ou a
soma passar do disponível, a chamada inteira falha (nada é gravado).
Resposta 200:
{
"rules": [
{ "id": "1e04...", "destination": "Maria Fornecedora", "percentage": "10.000000" }
]
}
### GET /v1/split/lookup
Escopo: `split:read`. Confirma um destino antes de salvar a regra.
GET /v1/split/lookup?query=maria@exemplo.com
Resolve o CPF, CNPJ ou e-mail em `query` para a conta que ele
pertence — a mesma confirmação que a tela de split do cliente mostra
("vai enviar para fulano?"). Use antes de PUT /v1/split para não
gravar uma regra apontando para o destino errado.
Resposta 200:
{
"sellerId": "8d6d608e-...",
"name": "Maria Fornecedora"
}
### PUT /v1/split/pix
Escopo: `split:write`. Split PERMANENTE para uma chave Pix FORA da plataforma
(fornecedor, sócio, outro banco). O dinheiro sai por Pix para a chave.
Corpo:
{
"lines": [
{ "label": "Fornecedor Pix", "keyType": "CNPJ", "key": "63473778000138", "percentage": "10", "feePayer": "SHARED" },
{ "ruleId": "9c2b...", "label": "Sócio", "percentage": "5" }
]
}
- Substitui a lista INTEIRA de linhas Pix (até 5 destinos POR TIPO DE ENTRADA). Mandar `"lines": []` remove todas.
- `operations` (opcional): em quais entradas a linha vale, uma lista com `PIX`, `TRANSFER`, `BOLETO`, `CARD`
e/ou `LINK`. Omitido numa linha que já existe = mantém o que ela tinha; omitido numa linha nova ou `[]` = TODAS
as entradas. Ex.: o sócio recebe 30% só do Pix (`["PIX"]`) e outra pessoa 100% só do boleto (`["BOLETO"]`).
A soma de 100% e o limite de 5 destinos valem entrada por entrada; a mesma chave pode repetir só em entradas diferentes.
- Linha NOVA: `label`, `keyType` (CPF, CNPJ, EMAIL, PHONE ou RANDOM), `key` completa e `percentage`.
Para MANTER uma chave que já existe, mande só o `ruleId` (a chave inteira nunca volta nas leituras).
- `feePayer` (opcional): quem arca com a tarifa do Pix — `SOURCE` (a sua conta), `DESTINATION` (quem recebe)
ou `SHARED` (dividida; padrão).
- Em cada tipo de entrada, a soma das linhas (conta + Pix) mais o que o administrador do white-label reservou nunca passa de 100%.
Se algo falhar, nada é gravado.
- O Pix só sai quando a venda LIQUIDA (boleto e cartão respeitam o prazo de liquidação). Fatia menor que
`pix.minimum` não é enviada e fica com a conta.
- Só funciona se `pix.enabled` e `pix.ready` forem true em `GET /v1/split`. Senão a chamada falha com 400
`pix_unavailable`.
Resposta 200:
{
"pixRules": [
{ "id": "9c2b...", "label": "Fornecedor Pix", "keyType": "CNPJ", "keyMasked": "**********0138",
"percentage": "10.000000", "feePayer": "SHARED" }
]
}
Erros (400, `error` estável, em minúsculas): `pix_unavailable`, `invalid_request` (formato) e os de regra do
split (percentual acima do livre, destino repetido, máximo de destinos).
### GET /v1/split/history
Escopo: `split:read`. O que já saiu desta conta por split (conta e Pix juntos).
GET /v1/split/history?kind=PIX&limit=20&offset=0
`kind` é opcional (`ACCOUNT` ou `PIX`); `limit` vai até 100. Resposta 200 (valores em decimal):
{
"items": [
{ "id": "5a1c...", "kind": "PIX", "createdAt": "2026-10-05T14:02:11.000Z", "amount": "10.00",
"fee": "0.40", "total": "10.40", "destination": "Fornecedor Pix", "keyMasked": "**********0138",
"status": "SENT" }
],
"total": 1, "limit": 20, "offset": 0,
"summary": { "sent": "10.00", "fees": "0.40", "pending": 0 }
}
`status`: `SENT`, `WAITING_RELEASE` (esperando o prazo de liquidação), `QUEUED`, `SENDING`, `FAILED`,
`CANCELLED`. O `summary` conta só o que de fato saiu (`SENT`).
---
## Folha de pagamento (`/v1/payroll`)
Pague funcionários por Pix, com as mesmas regras, limites e tarifas da folha do portal. Dois escopos:
`payroll:read` (só consulta) e `payroll:write` (tudo que altera).
**ATENÇÃO — `payroll:write` move dinheiro.** Funcionário com `autoPay: true` (padrão) é pago SOZINHO no dia do
pagamento, e uma folha agendada é paga SOZINHA na data. Por isso não existe escopo "só monta": quem cadastra ou
agenda já paga. Dê `payroll:write` somente à chave do servidor que precisa disso e guarde-a como guarda uma chave
`pix:send`.
Antes de tudo, confira se a folha está ligada: `GET /v1/payroll/overview` devolve `availability.enabled`
(o white-label liga o serviço e define a tarifa por pagamento) e, quando false, o motivo em `availability.reason`.
Não existem pela API (só no painel, com senha): exportar a planilha de funcionários (traria as chaves Pix
completas) e importar planilha. A chave Pix completa NUNCA é devolvida: as respostas trazem `pixKeyMasked`.
### GET /v1/payroll/overview
Escopo: `payroll:read`.
{
"availability": { "enabled": true, "reason": null, "feePerPayment": "0.80" },
"employees": { "active": 12, "monthlyPayroll": "30000.00" },
"upcoming": [
{ "id": "b7e1...", "kind": "SALARY", "kindLabel": "Salário", "reference": "2026-11", "title": "Salário 11/2026",
"scheduledFor": "2026-11-05", "status": "SCHEDULED", "totalAmount": "30000.00", "totalFee": "0.00",
"paidCount": 0, "failedCount": 0, "daysUntil": 31 }
],
"thisMonth": { "paid": "30000.00", "payments": 12 }
}
### Funcionários
- `GET /v1/payroll/employees?status=ACTIVE|INACTIVE&q=nome` — escopo `payroll:read`
- `GET /v1/payroll/employees/{id}` — detalhe com o histórico de pagamentos — `payroll:read`
- `POST /v1/payroll/employees` — cadastra (201, devolve `{ "id": "..." }`) — `payroll:write`
- `PUT /v1/payroll/employees/{id}` — atualiza (mesmo corpo do cadastro) — `payroll:write`
- `POST /v1/payroll/employees/{id}/terminate` — desliga (corpo opcional `{ "date": "2026-10-31", "reason": "..." }`)
- `POST /v1/payroll/employees/{id}/reactivate` — reativa
- `DELETE /v1/payroll/employees/{id}` — remove quem nunca recebeu pagamento
Corpo do cadastro:
{
"name": "Maria Souza",
"document": "12345678901",
"pixKeyType": "CPF",
"pixKey": "12345678901",
"salary": "2500.00",
"payDay": 5,
"businessDayRule": "PREVIOUS",
"advanceEnabled": true,
"advanceDay": 20,
"advancePercent": 40,
"autoPay": true,
"taxMode": "NET",
"dependents": 0
}
- `pixKeyType`: `CPF`, `CNPJ`, `EMAIL`, `PHONE` ou `RANDOM`.
- `salary`: valor LÍQUIDO a pagar (`taxMode: "NET"`) ou salário BRUTO (`taxMode: "CLT"`, e então INSS e IRRF vêm
calculados pelas tabelas do ano; FGTS é só informativo).
- `payDay`: dia do mês (1 a 31). `businessDayRule`: `PREVIOUS`, `NEXT` ou `EXACT`, para quando o dia não é útil.
- `advanceEnabled`/`advanceDay`/`advancePercent`: adiantamento mensal (percentual do salário).
- `autoPay`: true = a plataforma gera e paga a folha sozinha no dia.
- Trocar a chave Pix ou aumentar o salário vale também para os itens pendentes das folhas ainda não pagas.
Item da lista (`GET /v1/payroll/employees`):
{
"id": "3f0a...", "name": "Maria Souza", "document": null, "pixKeyType": "EMAIL",
"pixKeyMasked": "ma****@exemplo.com", "salary": "2500.00", "payDay": 5, "businessDayRule": "PREVIOUS",
"advanceEnabled": true, "advanceDay": 20, "advancePercent": "40.00", "autoPay": true, "taxMode": "NET",
"dependents": 0, "status": "ACTIVE", "terminatedAt": null, "nextPayment": "2026-11-05"
}
### GET /v1/payroll/runs/suggest
Escopo: `payroll:read`. O que pagar a cada funcionário ativo em um tipo de folha.
GET /v1/payroll/runs/suggest?kind=SALARY&reference=2026-11-05
`kind`: `SALARY`, `ADVANCE`, `THIRTEENTH_FIRST`, `THIRTEENTH_SECOND`, `VACATION`, `BONUS` ou `OTHER`.
Para CLT já vêm as linhas de desconto (INSS, IRRF) e o `fgts` informativo. Use para montar o `items` da folha.
{ "items": [
{ "employeeId": "3f0a...", "name": "Maria Souza", "salary": "2500.00", "base": "1500.00",
"suggested": "1500.00", "lines": [], "taxMode": "NET", "fgts": null,
"note": "Salário menos o adiantamento de 40%" } ] }
### POST /v1/payroll/runs
Escopo: `payroll:write`. Cria uma folha para uma data de hoje em diante (até 500 itens). Devolve 201 com
`{ "id": "..." }`.
{
"kind": "SALARY",
"title": "Salário 11/2026",
"scheduledFor": "2026-11-05",
"items": [
{ "employeeId": "3f0a...", "amount": "1500.00", "lines": [ { "label": "Bônus", "amount": "100.00" } ] }
]
}
- `lines`: ajustes opcionais (negativo = desconto, positivo = acréscimo); `amount` do item é o valor-base.
- A folha agendada é PAGA SOZINHA em `scheduledFor`. Não precisa chamar "pagar".
- O mesmo funcionário não é pago duas vezes no mesmo período e tipo: a segunda tentativa dá 400 `DUPLICATE_PERIOD`.
### Consultar e editar a folha
- `GET /v1/payroll/runs?status=...` e `GET /v1/payroll/runs/{id}` — `payroll:read`
- `PATCH /v1/payroll/runs/{id}` — corpo `{ "scheduledFor", "title", "note" }` (todos opcionais)
- `POST /v1/payroll/runs/{id}/items` — corpo `{ "employeeId": "..." }` (inclui mais um funcionário)
- `PUT /v1/payroll/runs/{id}/items/{itemId}` — corpo `{ "amount", "lines", "remove" }`
- `POST /v1/payroll/runs/{id}/cancel`
Editar e cancelar só vale enquanto a folha não começou a pagar; depois disso a resposta é 400 `NOT_EDITABLE`.
`GET /v1/payroll/runs/{id}` devolve a folha, cada item e um `preview`:
{
"id": "b7e1...", "kind": "SALARY", "title": "Salário 11/2026", "scheduledFor": "2026-11-05",
"status": "SCHEDULED", "totalAmount": "1600.00", "totalFee": "0.00", "paidCount": 0, "failedCount": 0,
"lastError": null, "editable": true,
"items": [
{ "id": "d41c...", "employeeId": "3f0a...", "employeeName": "Maria Souza", "pixKeyMasked": "ma****@exemplo.com",
"baseAmount": "1500.00", "lines": [ { "label": "Bônus", "amount": "100.00" } ], "amount": "1600.00",
"status": "PENDING", "fee": "0.00", "transactionId": null, "failureReason": null, "paidAt": null }
],
"preview": { "toPay": "1600.00", "feeEstimated": "0.80", "totalDebit": "1600.80", "balance": "5000.00",
"shortfall": null, "feeError": null }
}
`status` da folha: `SCHEDULED`, `AWAITING_FUNDS` (saldo insuficiente; tenta de novo quando entrar saldo),
`PROCESSING`, `COMPLETED`, `PARTIAL`, `FAILED`, `CANCELLED`.
`status` do item: `PENDING`, `SENT` (Pix enviado, aguardando o banco), `PAID`, `FAILED` (o banco confirmou que
NÃO saiu; valor e tarifa já voltaram à conta), `SKIPPED`.
### POST /v1/payroll/runs/{id}/pay
Escopo: `payroll:write`. Paga agora uma folha `SCHEDULED` ou `AWAITING_FUNDS`, sem esperar a data.
O dinheiro sai pelo Pix de saída da conta, com os limites e tarifas dela. Se o saldo não bastar, nada é enviado:
a folha vai para `AWAITING_FUNDS` e `lastError` diz quanto falta. Chamar duas vezes é seguro (só um processo pega a
folha). Resposta 200: a folha, no mesmo formato de `GET /v1/payroll/runs/{id}`. Acompanhe o desfecho pelo `GET`.
`POST /v1/payroll/runs/{id}/retry` (também `payroll:write`) repete SÓ os itens que o banco confirmou que não
saíram; nunca repete um Pix que pode ter saído.
### Histórico e comprovante
- `GET /v1/payroll/history?employeeId=&status=SENT|PAID|FAILED&from=2026-10-01&to=2026-10-31&limit=50&offset=0`
— `payroll:read`. Devolve `items`, `totals` (`paid`, `fees`, `count`) e `page`.
- `GET /v1/payroll/runs/{id}/items/{itemId}/receipt` — comprovante do pagamento de um item — `payroll:read`.
Erros: 401 `missing_credentials`/`invalid_key`; 403 `insufficient_scope`, `key_without_account` e
`not_available_by_api` (exportar/importar planilha); 404 para folha ou funcionário que não é da conta; 400 com
`code` estável (`NOT_EDITABLE`, `DUPLICATE_PERIOD`, `INVALID_PIX_KEY`, `INVALID_DOCUMENT`...).
---
### POST /v1/payment-links
Escopo: `pix:write`. Cria um link de pagamento.
Uma página hospedada que fica aberta e recebe de várias pessoas, até
ser cancelada ou expirar. Diferente da cobrança avulsa acima.
Quando usar cada um:
- **Cobrança avulsa** (`/v1/pix/charges`): o pedido já existe no seu
sistema e você só precisa do QR. Um pagamento, um QR.
- **Link** (`/v1/payment-links`): o valor vai ser divulgado — uma
vaquinha, uma mensalidade, um catálogo — e várias pessoas pagam no
mesmo endereço.
Corpo:
{
"description": "Mensalidade de setembro",
"amount": "99.90",
"amountOpen": false,
"singleUse": false,
"expiresInHours": 720,
"requirePayerName": true
}
- `description`: 2 a 140 caracteres. Obrigatório.
- `amount`: string decimal. Omita junto com `amountOpen: true` para
quem paga escolher o valor.
- `amountOpen`: quando true, quem paga define o valor, dentro de
`minAmount` e `maxAmount` se informados.
- `singleUse`: true fecha o link no primeiro pagamento. Padrão false.
- `maxUses`: fecha depois de N pagamentos.
- `expiresInHours`: 1 a 8760 (um ano).
- `requirePayerName`, `requirePayerDocument`, `requirePayerEmail`:
exigem o dado de quem paga antes de gerar o Pix.
Resposta 201:
{
"id": "4f21c8de-...",
"slug": "k3n8vq2p",
"url": "https://seu-banco.com/pagar/k3n8vq2p",
"description": "Mensalidade de setembro",
"amount": "99.90",
"amountOpen": false,
"singleUse": false,
"status": "open"
}
`url` é o endereço que você manda para quem vai pagar.
### GET /v1/payment-links
Escopo: `pix:read`. Os links da conta, com quanto cada um recebeu.
{
"items": [
{
"id": "4f21c8de-...",
"slug": "k3n8vq2p",
"url": "https://seu-banco.com/pagar/k3n8vq2p",
"description": "Mensalidade de setembro",
"amount": "99.90",
"singleUse": false,
"uses": 12,
"status": "open",
"received": "1198.80",
"payments": 12
}
]
}
### POST /v1/payment-links/{id}/cancel
Escopo: `pix:write`. Fecha o link.
Quem abrir depois vê que não está mais disponível. Os pagamentos já
recebidos continuam na conta.
### POST /v1/transfers
Escopo: `transfers:write`. Transfere entre contas da plataforma.
ATENÇÃO: exige o header `Idempotency-Key` (8 a 128 caracteres de
[A-Za-z0-9_.:-]). Sem ele a resposta é 400 `missing_idempotency_key`.
curl -X POST https://bank.apxbco.com.br/api/v1/transfers \
-H "Authorization: Bearer sk_live_sua_chave" \
-H "Idempotency-Key: pedido-8842" \
-H "Content-Type: application/json" \
-d '{"amount":"150.00","to":"@apelido","description":"Pagamento"}'
- `to`: aceita `@apelido`, `apelido` ou o UUID da conta.
---
## 4. Idempotência
Use `Idempotency-Key` em toda operação que move dinheiro. Reenviar a
mesma chave — mesma conta, mesmo valor do header — devolve o resultado
da primeira chamada em vez de executar de novo. É o que protege contra
timeout e clique duplo: sem isso, um app que reenvia a requisição por
segurança quando não recebe resposta a tempo pode gerar duas cobranças
ou duas transferências para o mesmo pedido.
Obrigatório em `POST /v1/transfers`, `POST /v1/pix/payouts` e
`POST /v1/pix/qr/pay` — a
chamada sem o header é recusada nessas duas rotas. Nas três rotas de
cobrança avulsa — `POST /v1/pix/charges`, `POST /v1/boleto/charges`
e `POST /v1/card/charges` — o header é OPCIONAL, mas fortemente
recomendado nas três, e ainda mais em cartão: lá, sem idempotência,
um retry de rede pode cobrar o cliente duas vezes de verdade, não só
gerar uma cobrança OPEN duplicada como em Pix.
A chave vale por conta: duas contas podem usar o mesmo valor de
`Idempotency-Key` sem conflito entre si.
---
## 5. Webhooks — confirmação de pagamento
Cadastre a URL no painel, em Credenciais. O sistema avisa quando o
pagamento acontece, em vez de você perguntar de tempos em tempos.
### Eventos
- `charge.paid` — a cobrança foi paga e o valor entrou na conta.
É ESTE que autoriza liberar o pedido. Vale para os três métodos
(Pix, boleto, cartão) e para os dois caminhos: cobrança avulsa e
pagamento feito num link.
- `charge.expired` — venceu sem pagamento. Serve para cancelar o
pedido em aberto.
- `transfer.completed` — uma transferência enviada chegou ao destino.
- `pix.payout.completed` — um Pix enviado (por chave ou por QR) chegou ao
destino. SÓ chega a quem assina este nome.
- `pix.payout.failed` — um Pix enviado não saiu; valor e tarifa já voltaram
à conta. SÓ chega a quem assina este nome.
- `receivable.released` — o prazo de um boleto/cartão venceu e o valor
passou a ficar disponível. SÓ chega a quem assina este nome.
- `receivable.anticipated` — o cliente antecipou recebíveis (`amount`,
`fee` e `net` em centavos). SÓ chega a quem assina este nome.
- `credit.drawn` — alguém usou o limite de crédito.
- `loan.approved` — um empréstimo foi aprovado.
- `investment.applied` — um aporte foi aplicado.
- `consortium.quota.approved` — uma cota de consórcio foi aprovada.
- `consortium.contemplated` — uma cota foi contemplada.
Para loja ou cardápio, assine apenas `charge.paid` e, se quiser
cancelar pedidos sozinho, `charge.expired`.
### O que chega
POST https://seu-sistema.com/webhooks
content-type: application/json
x-webhook-id: 9f2c1a44-...
x-webhook-event: charge.paid
x-webhook-timestamp: 1789002842
x-webhook-signature: 7b52009b64fd0a2a49e6d8a939753077792b0554...
{
"id": "9f2c1a44-...",
"type": "charge.paid",
"createdAt": "2026-09-09T21:14:02.000Z",
"data": {
"chargeId": "e636775c-...",
"sellerId": "c34b87db-...",
"amount": "4990",
"method": "PIX"
}
}
ATENÇÃO: `data.amount` vem em CENTAVOS (4990 = R$ 49,90), diferente
do resto da API, que usa string decimal. `data.chargeId` é o mesmo
`id` devolvido na criação. `data.method` diz como foi paga —
`PIX`, `BOLETO` ou `CARD` — sem precisar de uma chamada extra só
para descobrir isso.
ATENÇÃO (prazo de recebimento): se o white-label definiu prazo para boleto
ou cartão, `charge.paid` continua avisando que o PAGAMENTO foi confirmado
(pode liberar o pedido), mas o valor fica em `pending` (ver GET
/v1/balance) até o prazo vencer — aí sai `receivable.released`. Pix
sempre cai na hora.
Os eventos `pix.payout.completed`, `pix.payout.failed`,
`receivable.released` e `receivable.anticipated` têm o mesmo formato, com
`data` assim (valores em CENTAVOS):
{ "transactionId": "8f1a...", "sellerId": "c34b87db-...", "amount": "15000", "fee": "120" }
Em `receivable.released` o identificador é `receivableId` e vem `method`
(`BOLETO` ou `CARD`); em `receivable.anticipated` vêm `amount`, `fee` e
`net`.
### Conferindo a assinatura
A assinatura é o HMAC-SHA256 de `{timestamp}.{corpo}` em hexadecimal,
com o segredo do destino. O segredo aparece ao cadastrar a URL e pode
ser recuperado depois no painel, em Credenciais, no botão
"Ver segredo" ao lado do destino.
import { createHmac, timingSafeEqual } from "node:crypto";
function confere(corpoBruto, headers, segredo) {
const assinatura = headers["x-webhook-signature"];
const timestamp = Number(headers["x-webhook-timestamp"]);
// Entrega velha é entrega repetida: recuse.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const esperado = createHmac("sha256", segredo)
.update(`${timestamp}.${corpoBruto}`)
.digest("hex");
const a = Buffer.from(assinatura);
const b = Buffer.from(esperado);
return a.length === b.length && timingSafeEqual(a, b);
}
ATENÇÃO: use o corpo CRU, exatamente como chegou. Se você deixar o
framework fazer o parse do JSON e depois reserializar, os bytes mudam
e a assinatura nunca bate. No Express, use
`express.raw({ type: "application/json" })` nessa rota. No Next.js
App Router, use `await request.text()`. Este é o erro mais comum de
todos: o webhook chega, a assinatura falha, e o pagamento nunca
confirma.
### Regras obrigatórias
1. Responda 200 rápido. Processe depois, em fila. Demorar faz o envio
ser considerado falho e reenviado.
2. Espere repetição. O mesmo evento pode chegar duas vezes. Guarde o
`x-webhook-id` já processado e ignore repetidos, ou o pedido é
liberado duas vezes.
3. Não confie no valor recebido para creditar. Antes de liberar o
pedido, confirme com `GET /v1/pix/charges/{id}`. O webhook diz o
que olhar; a consulta diz o que é verdade.
4. Use HTTPS. URLs em HTTP não são aceitas.
5. Vinte falhas seguidas desativam o destino. Depois de corrigir seu
servidor, reative no painel em Credenciais, botão "Reativar" — o
segredo continua o mesmo.
---
## 6. Erros
A maioria vem como JSON com `error` (um slug estável, para checar por
código) e `message` (texto para log ou depuração, não confie no
texto em si — ele pode mudar):
401 missing_credentials Faltou o header Authorization
401 invalid_key Chave inválida, expirada ou revogada
403 insufficient_scope A chave não tem o escopo da rota
403 wrong_host Chave usada no domínio de outro banco
403 key_without_account Chave sem conta associada, não opera dinheiro
400 missing_idempotency_key POST /v1/transfers exige Idempotency-Key
400 invalid_idempotency_key Idempotency-Key fora do formato aceito
400 invalid_request Corpo inválido; veja "details" quando vier
404 not_found POST /v1/payment-links/{id}/cancel: link não existe
ATENÇÃO: nem todo 400/404 segue esse formato. Um valor abaixo do
mínimo, uma cobrança ou link que não existe, e a maioria das validações
de negócio (`POST /v1/pix/charges`, `POST /v1/payment-links`) hoje
devolvem o formato padrão do framework:
{ "statusCode": 400, "error": "Bad Request", "message": "Valor abaixo do mínimo por cobrança: mínimo R$ 5,00" }
{ "statusCode": 404, "error": "Not Found", "message": "Cobranca nao encontrada" }
Nesses casos, `error` é só a categoria HTTP ("Bad Request", "Not
Found") — quem for tratar o erro por código deve usar o
`statusCode` e ler `message` como texto para mostrar ou logar, não
comparar contra um valor fixo.
ATENÇÃO — validação de corpo em `POST /v1/boleto/charges` e
`POST /v1/card/charges`: quando um campo do corpo é inválido (CPF
curto demais, número de cartão fora do formato, mês de validade
errado etc.), a resposta é sempre este formato genérico do framework
— `{"statusCode":400,"error":"Bad Request","message":""}` — nunca o `error` estável em minúsculo. Exemplos
reais de `message` que podem vir: "Numero de cartao invalido", "Mes
invalido, use MM", "Ano invalido, use AAAA", "CVV invalido",
"Percentual inválido". Só a PRIMEIRA validação que falhar é
reportada — se dois campos estiverem errados, corrigir o primeiro e
reenviar pode revelar o segundo erro só na tentativa seguinte. Para
não depender desse ciclo, valide o formato de `card.number`,
`card.expiryMonth`, `card.expiryYear`, `card.cvv` e
`payer.document` no seu lado antes de chamar a API.
`POST /v1/transfers` é exceção: os erros de negócio saem com `error`
estável e minúsculo, vindo direto do código —
400 seller_not_found Destino não existe nesta conta
400 seller_inactive Destino existe mas está inativo
400 same_seller Origem e destino são a mesma conta
400 insufficient_funds Saldo insuficiente para a transferência
400 amount_invalid Valor não passa nas regras de negócio
400 limit_exceeded Excedeu limite de valor ou de operações
400 cross_tenant Destino não pertence a este banco
`POST /v1/pix/payouts` também sai com `error` estável e minúsculo:
400 insufficient_funds Saldo insuficiente para o valor mais a tarifa
400 no_provider A conta não tem Pix de saída habilitado
400 provider_rejected O provedor recusou o envio; nada saiu
400 outbound_blocked A conta está com as saídas bloqueadas
400 pending_reconciliation Sem resposta a tempo: o Pix PODE ter saído; não repita com outra chave
400 idempotency_conflict A Idempotency-Key já foi usada por outra conta
`POST /v1/pix/qr/quote` e `POST /v1/pix/qr/pay` saem com o mesmo `error`
estável, mais os do código Pix:
400 brcode_invalid Não parece um código Pix
400 brcode_malformed Código Pix incompleto ou malformado
400 brcode_crc Código adulterado ou não copiado inteiro (CRC não confere)
400 brcode_currency Só Pix em reais é aceito
400 brcode_amount Valor do código Pix inválido
400 brcode_payload A cobrança do código não pôde ser lida
400 brcode_unreachable Não foi possível consultar o endereço do código
400 brcode_url_blocked Endereço do código não é aceito (só HTTPS público)
400 qr_invalid A chave Pix do QR estático não é válida
400 qr_expired O código expirou
400 qr_not_active O código já foi pago ou não está mais ativo
400 qr_amount_changed O valor mudou desde a consulta; reconfirme com o valor novo
400 qr_amount_editable O código permite alterar o valor (ainda não suportado)
400 qr_not_supported A provedora da conta não paga QR Code
400 amount_required QR sem valor definido: informe "amount"
O `split` de `POST /v1/pix/charges` e as rotas `GET/PUT /v1/split` e
`GET /v1/split/lookup` saem com o mesmo `error` estável:
400 split_not_enabled Split não está habilitado nesta conta
400 too_many_destinations Mais de 5 linhas em "split"/"lines"
400 exceeds_available_percentage Soma das linhas passa de 100% (ou do que o admin deixou livre)
400 invalid_percentage "percentage" inválido em alguma linha
400 invalid_document "destination" não parece CPF, CNPJ ou e-mail válido
400 recipient_not_found Algum "destination" não existe neste banco
400 recipient_inactive A conta destino existe, mas está encerrada ou suspensa
400 self_split Um "destination" é a própria conta
400 duplicate_destination O mesmo destino aparece duas vezes
400 timeout_ambiguous Sem resposta do provedor a tempo; conciliar antes de reenviar
`POST /v1/boleto/charges` e `POST /v1/card/charges` saem com `error`
estável:
400 provider_unavailable Este tenant não tem provedor configurado para este método (boleto ou cartão)
400 provider_rejected O provedor ou o cartão recusou a cobrança; nada foi creditado
400 timeout_ambiguous Sem confirmação do provedor a tempo; não se sabe se o cartão foi cobrado
ATENÇÃO sobre `provider_rejected` em cartão: este MESMO slug cobre
dois casos diferentes — cartão recusado pela operadora (dados errados,
sem limite, cartão bloqueado) e falha técnica na comunicação com o
provedor. Hoje não existe um código separado para cada caso; a única
forma de diferenciar programaticamente é a própria falta de
diferenciação — trate os dois igual (cartão não foi cobrado, cobrança
marcada `cancelled`, pode pedir outro cartão ou tentar de novo) e use
o texto em `message` só para mostrar ao usuário ou logar, nunca para
decidir o fluxo. Se sua integração precisar registrar o motivo exato
da recusa para conciliação, guarde o texto de `message` junto do
pedido.
ATENÇÃO sobre `timeout_ambiguous` em cartão — diferente de
`provider_rejected`: aqui o provedor não respondeu a tempo, então NÃO
SE SABE se o cartão chegou a ser cobrado do outro lado. A cobrança
fica `open`, não `cancelled` — de propósito, para não afirmar um
desfecho que não é certo. NÃO tente cobrar de novo automaticamente
depois desse erro: confirme com o suporte ou aguarde antes de gerar
uma nova tentativa, ou seu cliente final pode acabar sendo cobrado
duas vezes se o provedor tiver processado a primeira depois do seu
timeout.
Outros `error` possíveis nas rotas de boleto e cartão antes mesmo de
chamar o provedor (a cobrança nem chega a nascer):
400 amount_required "amount" ausente ou zerado
400 operation_disabled Este tipo de cobrança está desligado para a conta
400 above_limit Valor acima do limite por operação configurado
400 below_limit Valor abaixo do mínimo por operação configurado
Não existe hoje um código equivalente para saldo insuficiente em
`POST /v1/pix/charges` (que só recebe, nunca debita a própria conta)
nem limite de taxa de chamadas (rate limiting) na API.
---
## 7. Formatos
- Dinheiro: string decimal com duas casas, `"150.00"`. NUNCA número —
ponto flutuante perde centavo. A exceção é `data.amount` no webhook,
que vem em centavos como string.
- Datas: ISO 8601 em UTC, `"2026-09-06T04:12:00.000Z"`.
- Identificadores: UUID. Não presuma ordem nem sequência.
---
## 8. Fluxo completo de uma loja
1. Cliente fecha o pedido no seu sistema.
2. Você chama `POST /v1/pix/charges` com o valor e o
`externalReference` do pedido.
3. Guarda o `id` retornado junto do pedido no seu banco.
4. Mostra o `copyPaste` para o cliente, e o QR gerado a partir dele.
5. O cliente paga.
6. Seu endpoint de webhook recebe `charge.paid`.
7. Você confere a assinatura.
8. Você chama `GET /v1/pix/charges/{id}` e confirma `status: "paid"`.
9. Só então libera o pedido.
O passo 8 não é opcional. É o que separa "recebi um aviso" de
"o dinheiro está na conta".
### Quando o caminho é o link
Se o valor é divulgado e várias pessoas pagam no mesmo endereço — uma
mensalidade, uma vaquinha —, crie um link com POST /v1/payment-links e
divulgue a `url`. Cada pagamento gera seu próprio `charge.paid`, e
os passos 6 a 9 valem igual. A diferença é que o link continua aberto
depois: um pagamento não o fecha, a menos que `singleUse` seja true.
### Fluxo completo de boleto
Mesma estrutura do Pix, com uma diferença central: a confirmação NUNCA
é imediata.
1. Cliente fecha o pedido, escolhe pagar no boleto.
2. Você chama `POST /v1/boleto/charges` com `amount`, `payer.name`,
`payer.document` (obrigatórios para boleto) e
`externalReference`. Envie `Idempotency-Key`.
3. Guarda o `id` retornado. Mostra `digitableLine` (e
`bankSlipUrl`, se vier) para o cliente imprimir ou pagar pelo
internet banking.
4. O pedido fica em espera — não libere nada ainda. Diferente do
Pix, aqui pode levar horas ou dias até o cliente pagar, e mais um
dia útil até o banco emissor confirmar para a plataforma.
5. Quando confirmado, seu endpoint de webhook recebe `charge.paid`
com `data.method: "BOLETO"`.
6. Confere a assinatura, depois chama
`GET /v1/boleto/charges/{id}` e confirma `status: "paid"`.
7. Só então libera o pedido.
Se o cliente não pagar até o vencimento, você recebe `charge.expired`
em vez de `charge.paid` — cancele o pedido em aberto no seu sistema.
### Fluxo completo de cartão
Aqui a diferença é a oposta do boleto: a resposta já vem com o
resultado, sem esperar webhook — MAS ainda existe um caminho
assíncrono para o caso de análise de risco.
1. Seu checkout coleta os dados do cartão diretamente (mantenha-os
fora de qualquer log ou armazenamento seu — veja o aviso de
PCI-DSS acima).
2. Você chama `POST /v1/card/charges` com `amount`, `payer`,
`card` e, fortemente recomendado, `Idempotency-Key`.
3. A resposta já traz o desfecho na maioria dos casos:
- `status: "paid"` — cobrado, dinheiro já está na conta. Libere o
pedido agora, sem esperar webhook.
- erro 400 `provider_rejected` — cartão recusado ou falha
técnica. Não foi cobrado. Peça outro cartão ou deixe o cliente
tentar de novo.
- `status: "awaiting_risk_analysis"` — ainda não se sabe o
resultado. NÃO libere o pedido e NÃO trate como erro. Espere o
webhook `charge.paid` (aprovado) ou `charge.expired`
(recusado depois da análise). Enquanto isso,
`GET /v1/card/charges/{id}` mostra `status: "open"`, não
`awaiting_risk_analysis` — não use o GET para decidir nada
nesse meio-tempo, só o webhook resolve.
- `status: "challenge_required"` + `challengeUrl` — o banco pediu
autenticação (3DS). Abra a URL em iframe para o pagador, não
libere o pedido e espere o webhook `charge.paid`.
4. Se o webhook chegar, confira a assinatura e só então libere —
mesma regra 3 do passo 8 geral: não confie no webhook sozinho,
confirme com GET antes de liberar.
Para a esmagadora maioria das cobranças (sem análise de risco), o
fluxo de cartão termina no passo 3: uma chamada, uma resposta, pedido
liberado.
---
## Checklist antes de ir para produção
- [ ] A chave está no servidor, fora do repositório
- [ ] O webhook usa o corpo cru para conferir a assinatura
- [ ] O webhook responde 200 antes de processar
- [ ] Eventos repetidos são ignorados pelo `x-webhook-id`
- [ ] O pedido só é liberado após `GET` confirmar `status: "paid"`
- [ ] Transferências, envios de Pix (payouts) e pagamentos por QR enviam `Idempotency-Key` (obrigatório)
- [ ] Depois de pagar um Pix (chave ou QR), o desfecho é acompanhado por `GET /v1/transactions/{id}` (`SETTLED`/`FAILED`); não existe webhook para isso
- [ ] Pagamento por QR: o valor confirmado pelo usuário vai em `amount`; `qr_amount_changed` reexibe o valor novo e pede outra confirmação
- [ ] `pending_reconciliation` nunca dispara um novo pagamento com outra chave
- [ ] Cobranças Pix e boleto enviam `Idempotency-Key` (recomendado, evita duplicar)
- [ ] Cobranças de cartão enviam `Idempotency-Key` (ainda mais importante aqui: sem ela um retry pode cobrar o cliente duas vezes de verdade)
- [ ] `awaiting_risk_analysis` no POST de cartão é tratado como "aguardando", nunca como sucesso ou erro — o pedido só libera com o webhook `charge.paid`
- [ ] Validação de CPF/CNPJ e dos campos do cartão feita no seu lado — a API só confere formato, não dígito verificador
- [ ] Valores tratados como string decimal, nunca float
- [ ] Folha: `payroll:write` só na chave do servidor que paga; funcionário com `autoPay` e folha agendada pagam sozinhos no dia
- [ ] Folha: antes de agendar, `GET /v1/payroll/overview` com `availability.enabled: true` e saldo para cobrir a folha mais as tarifas (`preview`)
- [ ] Split para chave Pix: `pix.enabled` e `pix.ready` true em `GET /v1/split` antes de `PUT /v1/split/pix`
- [ ] Homologou no sandbox (chave sk_sbx_) antes de usar chave real
- [ ] Ciente de que sk_test_ e sk_live_ movem dinheiro real (só sk_sbx_ é simulada)
- [ ] Se cobra cartão via API, ciente de que os dados do cartão trafegam pelo seu servidor e isso coloca sua integração em escopo PCI-DSS