Documentação
Pagamentos no seu app, em 1 request.
PIX, boleto e cartão pela mesma API. Links de pagamento, produtos, assinaturas, webhooks assinados e um MCP pro seu agente integrar sozinho. Comece no sandbox, com zero dinheiro real.
Introdução
Base da API: https://api.nimbuupay.com. Todo valor é em centavos (4990 = R$ 49,90) e a moeda é sempre BRL. O fluxo mínimo é: você cria uma cobrança, mostra o copia-e-cola ou o QR pro cliente, e o pagamento confirma via webhook.
Se você não quer construir tela de pagamento, pule a API de cobranças e use link de pagamento: o checkout pronto fica em nimbuupay.com/<slug>.
Autenticação
Toda chamada usa Authorization: Bearer <API_KEY>. Nunca exponha a chave no front-end: ela dá acesso total à sua conta, então chame sempre do seu backend.
| Campo | Tipo | Descrição |
|---|---|---|
| sk_test_… | sandbox | PIX e cartão simulados. Nada de dinheiro real, e você pode marcar como pago na mão. |
| sk_live_… | produção | Dinheiro de verdade. Só é liberada depois da aprovação do cadastro (KYC) no painel. |
A API é a mesma nos dois modos: virar pra produção é trocar a chave. Pra liberar a chave sk_live_, envie os documentos em Ativar produção no painel.
curl https://api.nimbuupay.com/v1/charges \ -H "Authorization: Bearer sk_test_..."
Quickstart
Crie sua primeira cobrança PIX:
curl -X POST https://api.nimbuupay.com/v1/charges \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-123" \
-d '{
"amount": 4990,
"description": "Pedido #123",
"customer": { "name": "Ana", "document": "12345678909" }
}'Cobranças
POST /v1/charges cria a cobrança.
| Campo | Tipo | Descrição |
|---|---|---|
| amount | int, obrigatório | Centavos. Mínimo 100 (R$ 1,00). |
| customer.name | string, obrigatório | Nome do pagador. |
| customer.document | string, obrigatório | CPF ou CNPJ. O dígito verificador é validado. |
| customer.email | string | Recomendado: é por onde o cliente recebe o comprovante. |
| customer.phone | string | Opcional. |
| description | string | Até 140 caracteres. Aparece pro pagador. |
| method | enum | pix (padrão), boleto ou credit_card. |
| installments | int | 1 a 12. Só vale com credit_card. Padrão 1. |
| card | objeto | Obrigatório quando method = credit_card. |
| expiresIn | int | Validade do PIX em segundos. Mínimo 60, padrão 3600. |
| metadata | objeto | Seus dados livres. Voltam no webhook, úteis pra amarrar ao seu pedido. |
Idempotência
Mande o header Idempotency-Key em toda criação. Se o mesmo valor chegar de novo, a API devolve a cobrança original em vez de criar outra. É o que protege você de cobrar o cliente duas vezes quando a rede cai no meio e o seu código tenta de novo.
{
"id": "ch_test_...",
"object": "charge",
"status": "pending",
"amount": 4990,
"currency": "BRL",
"method": "pix",
"pix": {
"qr_code": "00020126...6304XXXX",
"qr_code_base64": "data:image/png;base64,...",
"expires_at": "2026-..."
},
"paid_at": null
}GET /v1/charges/{id} consulta uma · GET /v1/charges lista (paginado).
Status possíveis
| Campo | Tipo | Descrição |
|---|---|---|
| pending | aguardando | Criada, ainda não paga. |
| paid | final | Paga e confirmada. Dispara charge.paid. |
| expired | final | Passou da validade sem pagamento. |
| failed | final | Recusada (típico de cartão). |
| canceled | final | Cancelada. |
| refunded | final | Estornada por INTEIRO. Estorno parcial segue paid, com amount_refunded > 0. |
No sandbox, POST /v1/charges/{id}/pay marca como paga e dispara o webhook de verdade. É assim que você testa o fluxo inteiro sem mover dinheiro.
PIX, boleto e cartão
Mesma rota, muda o method. O que volta na resposta é que muda.
PIX (padrão)
Volta pix.qr_code (o copia-e-cola) e pix.qr_code_base64 (a imagem pronta pra exibir). Confirmação em segundos, via webhook.
Boleto
{ "amount": 4990, "method": "boleto",
"customer": { "name": "Ana", "document": "12345678909" } }Volta boleto.url (página pra visualizar e imprimir), boleto.pdf e boleto.expiration. Compensação leva de 1 a 3 dias úteis, então não libere o produto na criação, libere no charge.paid.
Cartão de crédito
{
"amount": 4990,
"method": "credit_card",
"installments": 3,
"customer": { "name": "Ana", "document": "12345678909" },
"card": {
"number": "4111111111111111",
"holder_name": "ANA SOUZA",
"exp_month": 12,
"exp_year": 2030,
"cvv": "123"
}
}De 1 a 12 parcelas. O cartão é tokenizado pelo adquirente, então não guarde número de cartão no seu banco: além de ser risco, joga você dentro do escopo pesado de PCI sem necessidade. Cobrança de cartão pode voltar failed na hora, diferente do PIX.
Estornos
Devolver dinheiro é uma chamada, não um chamado pro suporte. Sem amount, volta tudo que ainda resta.
curl -X POST https://api.nimbuupay.com/v1/charges/ch_live_.../refund \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "amount": 1000, "reason": "Produto devolvido" }'{
"id": "re_live_...",
"object": "refund",
"charge_id": "ch_live_...",
"amount": 1000,
"reason": "Produto devolvido",
"status": "succeeded",
"created_at": "2026-..."
}Parcial não é o mesmo que total
A cobrança acumula amount_refunded. Enquanto sobra alguma coisa, o status continua paid — só quando tudo volta ela vira refunded. Nos dois casos sai um charge.refunded, então compare amount_refunded com amount antes de revogar o acesso do cliente: quem trata todo charge.refunded como cancelamento total corta o acesso de quem só pediu R$ 10 de volta.
| Campo | Tipo | Descrição |
|---|---|---|
| amount | int | Centavos a devolver. Ausente = tudo que resta. |
| reason | string | Até 240 caracteres. Aparece no painel e no extrato. |
| Idempotency-Key | header | Mesma chave devolve o mesmo estorno, nunca um segundo. |
GET /v1/charges/{id}/refunds lista o histórico — uma cobrança pode ter vários estornos parciais.
Quanto sai do seu saldo
Sai o valor devolvido ao cliente, não o líquido que você recebeu. Numa venda de R$ 100 você recebeu R$ 99,01 (a taxa de R$ 0,99 ficou no caminho); devolvendo os R$ 100, saem R$ 100 do seu saldo. A taxa não volta, porque ela é o custo que o banco cobrou na hora que o dinheiro entrou e ele não devolve esse custo. É como Stripe e as maquininhas funcionam.
Na prática: cada venda estornada por inteiro custa a taxa daquela venda. Estorno parcial custa proporcionalmente nada além do que você devolveu.
Saldo insuficiente não impede o estorno
Se você já sacou, o estorno passa mesmo assim e seu saldo fica negativo. Preferimos assim: segurar a devolução do seu cliente por causa do seu saldo geraria chargeback no cartão e reclamação no PIX, e as duas coisas custam mais caro pra você do que o negativo.
Enquanto o saldo estiver negativo, o saque fica bloqueado. As próximas vendas quitam sozinhas. Só acima de um teto de exposição a gente recusa o estorno, e aí é caso pra falar com a gente.
O que a API recusa
| Campo | Tipo | Descrição |
|---|---|---|
| 400 charge_not_refundable | estado | Só cobrança paga é estornável. |
| 400 already_refunded | estado | Já voltou por inteiro. |
| 400 refund_exceeds_charge | valor | Pediu mais do que ainda resta. |
| 400 refund_exposure_exceeded | saldo | O negativo passaria do teto da sua conta. Fale com a gente. |
O estorno só é registrado depois que o dinheiro sai de verdade: se o provedor recusar, sua cobrança fica intacta em vez de aparecer como estornada sem ter sido.
Links de pagamento
O caminho sem escrever tela: você cria o link, manda pro cliente e recebe. O checkout é hospedado por nós, já com PIX, boleto e cartão, cupom e order bump.
curl -X POST https://api.nimbuupay.com/v1/checkout-links \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "product_id": "prod_...", "label": "Campanha de lançamento" }'{
"id": "lnk_...",
"slug": "052bb5bb1a",
"url": "https://nimbuupay.com/052bb5bb1a"
}| Campo | Tipo | Descrição |
|---|---|---|
| product_id | string | Produto que o link vende. Use isto ou products[]. |
| price_id | string | Preço específico do produto (quando há mais de um). |
| products | array | Vários produtos no mesmo link, com seletor no checkout. |
| label | string | Nome interno, só pra você identificar no painel. |
| success_url | string | Pra onde mandar o cliente depois de pagar. |
| return_url | string | Pra onde voltar se ele desistir. |
| allow_coupons | boolean | Mostra o campo de cupom no checkout. |
| require_address | boolean | Pede endereço. Ligue se você envia produto físico. |
GET /v1/checkout-links lista os seus. O link fica na raiz do domínio (nimbuupay.com/<slug>). Links antigos no formato /l/<slug> continuam funcionando e redirecionam sozinhos.
Produtos
Produto é o que você vende; preço é quanto custa. Separar os dois deixa você ter mais de um preço (mensal e anual, por exemplo) pro mesmo produto.
curl -X POST https://api.nimbuupay.com/v1/products \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Curso de Programação",
"price": { "amount": 19700 },
"payment_methods": ["pix", "credit_card"]
}'| Campo | Tipo | Descrição |
|---|---|---|
| name | string, obrigatório | Nome que o cliente vê. |
| description | string | Descrição exibida no checkout. |
| price.amount | int | Centavos. |
| price.amount_type | enum | fixed (padrão), custom (o cliente escolhe) ou free. |
| prices | array | Vários preços pro mesmo produto. |
| recurring_interval | enum | month ou year. Define assinatura em vez de venda única. |
| trial_days | int | Dias de teste grátis antes da primeira cobrança. |
| setup_fee | int | Taxa única cobrada junto da primeira parcela. |
| payment_methods | array | Quais métodos liberar: pix, boleto, credit_card. |
| visibility | enum | public ou private. |
| metadata | objeto | Seus dados livres. |
GET /v1/products lista (aceita ?status=, ?q= e ?sort=name|recent) · GET /v1/products/{id} consulta · PATCH /v1/products/{id} edita · POST /v1/products/{id}/archive arquiva.
Arquivar não apaga: o produto some das listas novas mas os links e as cobranças antigas continuam íntegros. Não existe deletar de propósito, porque apagar um produto quebraria o histórico financeiro que já aconteceu.
Assinaturas
Duas portas, e as duas geram os mesmos eventos. Ou você cria um produto recorrente e um link, e a assinatura nasce quando o cliente paga — sem escrever tela. Ou você já tem os dados do cliente e cria direto pela API.
curl -X POST https://api.nimbuupay.com/v1/subscriptions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"interval": "monthly",
"value": 4990,
"method": "credit_card",
"trial_days": 7,
"customer": { "name": "Ana", "document": "12345678909", "email": "ana@ex.com", "phone": "11999999999" },
"card": { "number": "4111111111111111", "holder_name": "ANA SOUZA", "exp_month": 12, "exp_year": 2030, "cvv": "123" }
}'| Campo | Tipo | Descrição |
|---|---|---|
| interval | enum, obrigatório | monthly ou yearly. |
| value | int, obrigatório | Centavos de CADA ciclo. Mínimo 100. |
| method | enum, obrigatório | credit_card (auto-débito) ou pix/boleto (uma cobrança por ciclo). |
| customer | objeto, obrigatório | Mesmo formato da cobrança. No live, e-mail e telefone são exigidos. |
| card | objeto | Obrigatório em credit_card. Trafega só em memória: guardamos last4 e bandeira. |
| trial_days | int | Teste grátis. Só no cartão: ele é salvo hoje e a 1ª cobrança fica pra depois. |
| description | string | Até 140 caracteres. |
A resposta traz first_charge_id. No cartão ela já vem paga; em pix e boleto nasce pendente — consulte GET /v1/charges/{first_charge_id} pra pegar o QR ou a linha digitável e mostrar pro cliente.
| Campo | Tipo | Descrição |
|---|---|---|
| POST /v1/subscriptions | cria | Assinatura direto pela API, sem checkout. |
| GET /v1/subscriptions | lista | Todas as assinaturas da sua conta (paginado). |
| GET /v1/subscriptions/{id} | consulta | Status, intervalo, próxima cobrança. |
| POST /v1/subscriptions/{id}/cancel | cancela | Interrompe as cobranças futuras. |
Cancelamento vale pras cobranças futuras e não estorna o que já foi pago — pra devolver dinheiro, use estorno. Você é avisado por subscription.created e subscription.canceled.
O teste grátis tem anti-abuso embutido: se o mesmo CPF/CNPJ já usou um teste na sua conta, a assinatura nasce cobrando desde o primeiro ciclo. Sem isso, trocar de link renovaria o teste pra sempre.
Webhooks
Registre um endpoint pra ser notificado. O secret só aparece uma vez, na criação: guarde na hora, não dá pra recuperar depois.
POST /v1/webhooks
{ "url": "https://seuapp.com/webhooks/nimbuu", "events": ["charge.paid"] }Eventos disponíveis
| Campo | Tipo | Descrição |
|---|---|---|
| charge.paid | cobrança | Pagamento confirmado. É aqui que você libera o produto. |
| charge.failed | cobrança | Recusada. Típico de cartão. |
| charge.expired | cobrança | Venceu sem pagamento. |
| charge.canceled | cobrança | Cancelada antes de pagar. |
| charge.refunded | cobrança | Estornada, total OU parcialmente. Compare amount_refunded com amount antes de revogar o acesso. |
| subscription.created | assinatura | Nova assinatura ativa. |
| subscription.canceled | assinatura | Assinatura encerrada. |
Validar a assinatura (importante)
A entrega vem com Nimbuu-Signature: t=<ts>,v1=<hmac>. Valide o HMAC SHA256(secret, "<t>.<rawBody>") em tempo constante, usando o corpo cru (não o JSON já parseado, senão a assinatura não bate). O t serve pra rejeitar replay.
Sem essa validação, qualquer um que descubra sua URL pode enviar um charge.paid falso e liberar produto sem pagar.
import { verifyWebhook } from "nimbuu-pay";
const ok = verifyWebhook(rawBody, req.headers["nimbuu-signature"], process.env.NIMBUU_WEBHOOK_SECRET!);
if (!ok) return res.status(400).end();GET /v1/webhooks lista · DELETE /v1/webhooks/{id} remove. Entrega com falha é reenviada com backoff, então responda 2xx rápido e processe depois: se você demorar, vai receber o mesmo evento de novo.
Paginação
Toda listagem devolve no máximo 100 itens por página, no mesmo envelope:
{
"object": "list",
"data": [ ... ],
"has_more": true,
"next_cursor": "ch_live_..."
}GET /v1/charges?limit=50 GET /v1/charges?limit=50&starting_after=ch_live_... # a próxima página
| Campo | Tipo | Descrição |
|---|---|---|
| limit | int | 1 a 100. Ausente = 100. Fora da faixa é 400 invalid_limit, não um corte silencioso. |
| starting_after | string | O next_cursor da página anterior. Cursor de outra conta ou inexistente é 400 invalid_cursor. |
| has_more | bool | Tem mais página depois desta. |
| next_cursor | string|null | Id do último item entregue. null quando acabou. |
O cursor aponta pra um registro, não pra uma posição. Isso importa: com ?page=2, uma venda nova entrando enquanto você varre empurra a lista pra baixo e você lê a mesma cobrança duas vezes (ou pula uma). Com cursor, não.
Vale pra /v1/charges, /v1/subscriptions, /v1/products, /v1/checkout-links e /v1/webhooks. No SDK, charges.listAll() percorre tudo sozinho:
for await (const charge of nimbuu.charges.listAll()) {
// ...
}Ainda assim: pra saber de pagamento, reagir a webhook é melhor que listar em laço. A varredura serve pra conciliação, não pra descobrir venda.
Limite de taxa
Por chave de API, em janelas de um minuto:
| Campo | Tipo | Descrição |
|---|---|---|
| 300 / min | tudo | Todas as requisições autenticadas por chave. |
| 60 / min | escrita | POST, PATCH, PUT e DELETE. Sub-teto do de cima. |
O limite é por chave, não por IP — o servidor do seu cliente pode dividir IP com meio mundo sem pagar por isso.
RateLimit-Limit: 300 RateLimit-Remaining: 287 RateLimit-Reset: 42 # segundos até a janela reiniciar
Estourou, vem 429 rate_limited com Retry-After. Espere o que ele mandar em vez de tentar de novo na hora: repetir no mesmo segundo só queima a janela seguinte.
O checkout público tem proteção própria e mais rígida (bot, throttle por IP e por link, e cooldown contra teste de cartão). Ela não conta contra estes limites: são caminhos diferentes.
SDK TypeScript
npm i nimbuu-pay
import { NimbuuPay } from "nimbuu-pay";
const nimbuu = new NimbuuPay(process.env.NIMBUU_API_KEY!);
const charge = await nimbuu.charges.create({
amount: 4990,
customer: { name: "Ana", document: "12345678909" },
});
console.log(charge.pix?.qr_code); // copia-e-colaMCP — seu agente integra sozinho
O diferencial AI-native: adicione o Nimbuu Pay ao Cursor / Claude e peça “adicione PIX no meu checkout”. O agente cria cobranças e configura webhooks por você.
{
"mcpServers": {
"nimbuu-pay": {
"command": "npx",
"args": ["-y", "nimbuu-pay-mcp"],
"env": { "NIMBUU_API_KEY": "sk_test_..." }
}
}
}Tools: nimbuu_create_charge, nimbuu_get_charge, nimbuu_list_charges, nimbuu_refund_charge, nimbuu_create_subscription, nimbuu_simulate_payment, nimbuu_quickstart.
Erros
Sempre JSON, no mesmo formato: { "error": { "type", "code", "message" } }
| Campo | Tipo | Descrição |
|---|---|---|
| 401 no_api_key | auth | Faltou o header Authorization. |
| 401 invalid_api_key | auth | Chave inválida, revogada ou de outro modo. |
| 400 invalid_request | payload | Campo faltando ou fora do formato: amount < 100, CPF/CNPJ com dígito verificador inválido, e-mail malformado. |
| 400 insufficient_balance | saldo | Saldo insuficiente pra operação. |
| 400 invalid_limit | paginação | limit fora de 1–100. |
| 400 invalid_cursor | paginação | starting_after não é um registro desta conta. |
| 400 charge_not_refundable | estorno | Só cobrança paga é estornável. |
| 400 refund_exceeds_charge | estorno | Pediu mais do que ainda resta. |
| 400 refund_exposure_exceeded | estorno | O saldo negativo passaria do teto da conta. |
| 404 charge_not_found | recurso | Também webhook_not_found, product_not_found. |
| 429 rate_limited | limite | Estourou o limite da chave. Vem com Retry-After. |
| 429 too_many_attempts | limite | Vem com Retry-After dizendo quando tentar de novo. |
Limites e o que ainda não existe
Preferimos dizer o que falta a te deixar descobrir em produção:
- · Assinatura não muda de plano no meio do caminho. Não existe upgrade/downgrade com valor proporcional: pra trocar de plano, cancele a assinatura e crie outra. O que já foi cobrado não é ajustado sozinho.
- · Estorno de PIX tem prazo. A regra é do arranjo, não nossa: passado o prazo do provedor,
POST /refunddevolve erro e a devolução vira transferência manual. - · Estorno pode deixar seu saldo negativo. Ele debita o valor devolvido, e a taxa da venda não volta. Com saldo negativo o saque fica bloqueado até as próximas vendas quitarem.
- · As listagens não têm filtro por período nem por status.Dá pra paginar e ordenar, mas não pra pedir “as pagas de setembro”. Filtre no seu lado, ou guarde o que chega por webhook.
- · Não há listagem global de estornos. Só por cobrança (
GET /v1/charges/{id}/refunds). - · Cliente não é um objeto de 1ª classe. Ele existe dentro da cobrança; não há
/v1/customerspra criar, editar ou reusar.
Já resolvido, e documentado acima: paginação por cursor em todas as listagens, estorno pela API (total e parcial), criação de assinatura pela API e limite de taxa publicado.