# API de a plataforma Este arquivo descreve a API de a plataforma 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://SEU-BANCO/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 - `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://SEU-BANCO/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. Não simula: /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`). ### 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. 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", "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`. ### 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", "rules": [ { "id": "1e04...", "destination": "Maria Fornecedora", "percentage": "10.000000" } ], "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 pode ser usado pela própria conta (100% menos o que o admin já reservou, menos o que a própria conta já configurou). - `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). Mandar `"lines": []` remove todas. - 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). - A soma de TODAS as 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://SEU-BANCO/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