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.

CampoTipoDescrição
sk_test_…sandboxPIX e cartão simulados. Nada de dinheiro real, e você pode marcar como pago na mão.
sk_live_…produçãoDinheiro 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.

bash
curl https://api.nimbuupay.com/v1/charges \
  -H "Authorization: Bearer sk_test_..."

Quickstart

Crie sua primeira cobrança PIX:

bash
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.

CampoTipoDescrição
amountint, obrigatórioCentavos. Mínimo 100 (R$ 1,00).
customer.namestring, obrigatórioNome do pagador.
customer.documentstring, obrigatórioCPF ou CNPJ. O dígito verificador é validado.
customer.emailstringRecomendado: é por onde o cliente recebe o comprovante.
customer.phonestringOpcional.
descriptionstringAté 140 caracteres. Aparece pro pagador.
methodenumpix (padrão), boleto ou credit_card.
installmentsint1 a 12. Só vale com credit_card. Padrão 1.
cardobjetoObrigatório quando method = credit_card.
expiresInintValidade do PIX em segundos. Mínimo 60, padrão 3600.
metadataobjetoSeus 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.

json
{
  "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

CampoTipoDescrição
pendingaguardandoCriada, ainda não paga.
paidfinalPaga e confirmada. Dispara charge.paid.
expiredfinalPassou da validade sem pagamento.
failedfinalRecusada (típico de cartão).
canceledfinalCancelada.
refundedfinalEstornada 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

json
{ "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

json
{
  "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.

bash
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" }'
json
{
  "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.

CampoTipoDescrição
amountintCentavos a devolver. Ausente = tudo que resta.
reasonstringAté 240 caracteres. Aparece no painel e no extrato.
Idempotency-KeyheaderMesma 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

CampoTipoDescrição
400 charge_not_refundableestadoSó cobrança paga é estornável.
400 already_refundedestadoJá voltou por inteiro.
400 refund_exceeds_chargevalorPediu mais do que ainda resta.
400 refund_exposure_exceededsaldoO 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.

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.

bash
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" }'
json
{
  "id": "lnk_...",
  "slug": "052bb5bb1a",
  "url": "https://nimbuupay.com/052bb5bb1a"
}
CampoTipoDescrição
product_idstringProduto que o link vende. Use isto ou products[].
price_idstringPreço específico do produto (quando há mais de um).
productsarrayVários produtos no mesmo link, com seletor no checkout.
labelstringNome interno, só pra você identificar no painel.
success_urlstringPra onde mandar o cliente depois de pagar.
return_urlstringPra onde voltar se ele desistir.
allow_couponsbooleanMostra o campo de cupom no checkout.
require_addressbooleanPede 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.

bash
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"]
  }'
CampoTipoDescrição
namestring, obrigatórioNome que o cliente vê.
descriptionstringDescrição exibida no checkout.
price.amountintCentavos.
price.amount_typeenumfixed (padrão), custom (o cliente escolhe) ou free.
pricesarrayVários preços pro mesmo produto.
recurring_intervalenummonth ou year. Define assinatura em vez de venda única.
trial_daysintDias de teste grátis antes da primeira cobrança.
setup_feeintTaxa única cobrada junto da primeira parcela.
payment_methodsarrayQuais métodos liberar: pix, boleto, credit_card.
visibilityenumpublic ou private.
metadataobjetoSeus 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.

bash
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" }
  }'
CampoTipoDescrição
intervalenum, obrigatóriomonthly ou yearly.
valueint, obrigatórioCentavos de CADA ciclo. Mínimo 100.
methodenum, obrigatóriocredit_card (auto-débito) ou pix/boleto (uma cobrança por ciclo).
customerobjeto, obrigatórioMesmo formato da cobrança. No live, e-mail e telefone são exigidos.
cardobjetoObrigatório em credit_card. Trafega só em memória: guardamos last4 e bandeira.
trial_daysintTeste grátis. Só no cartão: ele é salvo hoje e a 1ª cobrança fica pra depois.
descriptionstringAté 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.

CampoTipoDescrição
POST /v1/subscriptionscriaAssinatura direto pela API, sem checkout.
GET /v1/subscriptionslistaTodas as assinaturas da sua conta (paginado).
GET /v1/subscriptions/{id}consultaStatus, intervalo, próxima cobrança.
POST /v1/subscriptions/{id}/cancelcancelaInterrompe 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.

json
POST /v1/webhooks
{ "url": "https://seuapp.com/webhooks/nimbuu", "events": ["charge.paid"] }

Eventos disponíveis

CampoTipoDescrição
charge.paidcobrançaPagamento confirmado. É aqui que você libera o produto.
charge.failedcobrançaRecusada. Típico de cartão.
charge.expiredcobrançaVenceu sem pagamento.
charge.canceledcobrançaCancelada antes de pagar.
charge.refundedcobrançaEstornada, total OU parcialmente. Compare amount_refunded com amount antes de revogar o acesso.
subscription.createdassinaturaNova assinatura ativa.
subscription.canceledassinaturaAssinatura 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.

ts
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:

json
{
  "object": "list",
  "data": [ ... ],
  "has_more": true,
  "next_cursor": "ch_live_..."
}
bash
GET /v1/charges?limit=50
GET /v1/charges?limit=50&starting_after=ch_live_...   # a próxima página
CampoTipoDescrição
limitint1 a 100. Ausente = 100. Fora da faixa é 400 invalid_limit, não um corte silencioso.
starting_afterstringO next_cursor da página anterior. Cursor de outra conta ou inexistente é 400 invalid_cursor.
has_moreboolTem mais página depois desta.
next_cursorstring|nullId 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:

ts
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:

CampoTipoDescrição
300 / mintudoTodas as requisições autenticadas por chave.
60 / minescritaPOST, 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.

bash
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

ts
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-cola

MCP — 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ê.

json
{
  "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" } }

CampoTipoDescrição
401 no_api_keyauthFaltou o header Authorization.
401 invalid_api_keyauthChave inválida, revogada ou de outro modo.
400 invalid_requestpayloadCampo faltando ou fora do formato: amount < 100, CPF/CNPJ com dígito verificador inválido, e-mail malformado.
400 insufficient_balancesaldoSaldo insuficiente pra operação.
400 invalid_limitpaginaçãolimit fora de 1–100.
400 invalid_cursorpaginaçãostarting_after não é um registro desta conta.
400 charge_not_refundableestornoSó cobrança paga é estornável.
400 refund_exceeds_chargeestornoPediu mais do que ainda resta.
400 refund_exposure_exceededestornoO saldo negativo passaria do teto da conta.
404 charge_not_foundrecursoTambém webhook_not_found, product_not_found.
429 rate_limitedlimiteEstourou o limite da chave. Vem com Retry-After.
429 too_many_attemptslimiteVem 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 /refund devolve 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/customers pra 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.

Pronto pra começar?

Crie sua conta e pegue uma chave de sandbox em segundos.

Criar conta grátis