# ribbo API: documentação completa > Cobrança recorrente para quem constrói SaaS. Leia e gerencie assinaturas (que nascem no checkout da ribbo), consulte as capacidades (entitlements) de cada cliente, processe estornos, gere links de pagamento e receba webhooks assinados. A recorrência é gerida pela ribbo: você nunca fala com o gateway. Contrato OpenAPI 3.1: https://dash.ribbo.app/openapi.json · Referência: https://dash.ribbo.app/docs/reference --- # Integração com a ribbo A **ribbo** é a plataforma de cobrança recorrente para assinaturas de qualquer tipo. Com esta API o seu sistema (app, site, backend) **lê e gerencia assinaturas**, **consulta as capacidades (entitlements)** de cada cliente, **processa estornos**, **gera links de pagamento** e **recebe webhooks assinados** a cada evento de cobrança. A recorrência é **gerida pela ribbo**: o seu sistema nunca fala com o gateway de pagamento, nunca vê número de cartão e não precisa agendar cobranças. Você declara *o que* o cliente tem direito; a ribbo cuida de *cobrar* e te avisa *o que aconteceu*. > **A assinatura nasce no checkout da ribbo.** Não existe endpoint para criar assinatura: o cliente > assina pela página de checkout e a API serve para ler e operar as assinaturas que já existem. ## Do que a API é capaz | Área | Você consegue | |---|---| | **Assinaturas** | listar, ler o estado atual, cancelar (na hora ou no fim do período) e desfazer o cancelamento agendado | | **Planos** | trocar de plano (upgrade imediato com proração, downgrade agendado), cancelar troca agendada | | **Cobrança** | cobrar agora (cartão salvo), reagendar a próxima cobrança, estornar (total ou parcial) | | **Links** | link de pagamento da fatura aberta, 2ª via do Pix de um pedido, link de renovação antecipada, link de troca de forma de pagamento | | **Clientes** | atualizar e-mail, nome e telefone, listar assinaturas e histórico de pagamentos | | **Entitlements** | consultar as capacidades vivas de um cliente (o que liberar no seu SaaS) | | **Indicações** | obter link/código de indicação e progresso das recompensas | | **Campanhas** | gerar e listar links de renovação de uma campanha | | **Webhooks** | receber 19 tipos de evento assinados (assinatura, fatura, pedido, pagamento, indicação) | | **IA** | servidor MCP para o seu assistente consultar e operar a cobrança ([guia](/docs/mcp)) | ## Gateways A cobrança roda na conta de gateway do próprio vendedor. O seu sistema não precisa saber qual é: o campo `gateway_provider` do pagamento informa, se você quiser. | Gateway | Métodos | Valor mínimo por cobrança | |---|---|---| | **Stone (Pagar.me)**, `pagarme` | cartão (até 18x), Pix (também na assinatura), Google Pay | sem mínimo | | **Stripe**, `stripe` | cartão, Apple Pay e Google Pay (sem Pix) | R$ 0,50 | | **Asaas**, `asaas` | Pix (também na assinatura) e cartão na página segura do Asaas | R$ 5,00 | Uma cobrança acima de zero e abaixo do mínimo do gateway não é enviada: a oferta, o cupom e a troca de plano são validados contra esse mínimo. ## Como a integração funciona 1. Você gera uma **API key** (`bk_...`) no painel, em **Configurações → API keys**. 2. Chama a API REST com `Authorization: Bearer bk_...`. Tudo em JSON, dinheiro em centavos. 3. Cadastra um **endpoint de webhook** em **Configurações → Webhooks** e valida a assinatura de cada evento. 4. Decide o acesso do cliente no seu SaaS pelo campo `status` (nunca pela presença de `entitlements`, ver a [regra de ouro](/docs/webhooks)). > **Para IA:** o contrato completo, legível por máquina, está em [`/openapi.json`](/openapi.json) > (OpenAPI 3.1) e resumido em [`/llms.txt`](/llms.txt). Aponte o seu agente para essas URLs, ou > instale o [servidor MCP](/docs/mcp). --- # Quickstart Em 3 passos você faz a primeira chamada e recebe o primeiro webhook. ## 1. Gere uma API key No painel, **Configurações → API keys**, dê um nome e clique em **Gerar**. Copie a chave `bk_...` na hora: ela é mostrada **uma única vez**. Escolha o escopo: `read` (leitura) ou `write` (leitura + ações). ## 2. Faça uma chamada Consultar as capacidades de um cliente (leitura, escopo `read` basta): ```bash curl "https://api.ribbo.app/v1/entitlements?external_id=cliente-123" \ -H "Authorization: Bearer bk_sua_chave" ``` Resposta: ```json { "customer_id": "cus_01J…", "subscriptions": [ { "id": "sub_01J…", "status": "active", "plan_id": "plan_01J…", "interval": "month", "current_period_end": "2026-09-01T00:00:00Z", "entitlements": { "seats": 5, "api": true } } ], "merged_entitlements": { "seats": 5, "api": true } } ``` > Cliente que ainda não existe na ribbo devolve **200** com `customer_id: null`, nunca 404. ## 3. Receba um webhook Cadastre a URL do seu endpoint no painel (**Configurações → Webhooks**), escolha os eventos e [valide a assinatura](/docs/webhooks) de cada POST. A cada fatura paga, troca de plano, suspensão etc., a ribbo envia um `POST` assinado para você. ## Próximos passos - [Autenticação e escopos](/docs/authentication) - [Assinaturas](/docs/subscriptions) - [Webhooks e a regra de ouro](/docs/webhooks) - [Servidor MCP para o seu assistente de IA](/docs/mcp) - Referência completa: [/docs/reference](/docs/reference) --- # Autenticação Toda chamada usa **Bearer token** com a sua API key (prefixo `bk_`): ``` Authorization: Bearer bk_sua_chave_aqui ``` Gere e revogue chaves no painel (**Configurações → API keys**). A chave em claro aparece **uma única vez** na criação; depois só o *preview* mascarado (`bk_AbC…wXyZ`). Revogar preserva a evidência (a chave fica inativa, não some). ## Escopos Cada chave tem um escopo: | Escopo | Pode | |---|---| | `read` | só **leitura** (consultar assinaturas, entitlements, pagamentos, links de leitura) | | `write` | leitura **+ ações que movem dinheiro ou estado** (trocar plano, estornar, cancelar, cobrar agora, gerar links de renovação…) | Uma chave `read` chamando uma rota de escrita recebe **403** `insufficient_scope`. Use chaves `read` em integrações que só observam, e `write` só onde precisa agir (princípio do menor privilégio). ## Erros de autenticação - **401** `{"error":"não autorizado"}`: sem header, chave inválida ou revogada, ou tenant suspenso. - **403** `{"error":"insufficient_scope"}`: chave `read` numa rota de escrita. - **403** `{"error":"account_locked"}`: a sua conta ribbo está em modo só leitura. Consultas seguem liberadas, e também corrigir dados de cliente, estornar e cancelar. ## Segurança - A chave identifica o **seu tenant**. Dados de outro tenant retornam **404** (indistinguível de "não existe"): nunca há vazamento cross-tenant. - Guarde a chave como segredo (variável de ambiente ou cofre). Nunca a exponha no front-end. --- # Convenções Valem para toda a API. ## Dinheiro em centavos Todo valor é um **inteiro em centavos**. `amount_cents: 1990` = R$ 19,90. **Nunca há float.** A moeda vem no campo `currency` em minúsculo (ex.: `"brl"`). ## IDs com prefixo Os IDs são ULIDs com prefixo, ordenáveis por tempo: | Prefixo | Recurso | |---|---| | `cus_` | cliente | | `sub_` | assinatura | | `inv_` | fatura | | `pay_` | pagamento | | `ord_` | pedido avulso | | `plan_` | plano | | `offer_` | oferta | | `evt_` | evento de webhook | O plano é identificado por **`plan_id`** (o `plan_name` vem junto, só para exibição) e a oferta por **`offer_id`**. O seu **`external_id`** do cliente é a chave que **você** controla e usa para casar com o seu sistema: as rotas de cliente (`/v1/customers/{external_id}/...`) usam ele. ## Datas UTC, formato ISO-8601 (ex.: `2026-09-01T00:00:00Z`). Campos de data podem vir `null` (ex.: `current_period_end` antes da primeira cobrança). ## Paginação por cursor (keyset) Listas usam cursor, não offset. Passe `?limit=` (default 100, máx 500) e `?after=`; a resposta traz `{ "results": [...], "next": "" | null }`. Pagine até `next` ser `null`. ```bash curl "https://api.ribbo.app/v1/customers/cliente-123/payments?limit=50" -H "Authorization: Bearer bk_..." # use o "next" da resposta como ?after= na próxima página ``` ## Erros O corpo de erro é `{"error":"mensagem"}`. Algumas rotas incluem campos extras, como `code` (ex.: `data_passada` no reagendamento), `status` ou `requires_confirmation`. Exceções: o **404** de um recurso que não existe e o **429** podem vir no formato `{"detail":"mensagem"}`, e o estorno recusado pelo gateway volta `{"level":"error","message":"..."}`. Decida pelo código HTTP. | Código | Significado | |---|---| | `400` | input inválido (ex.: campo obrigatório ausente) | | `401` | não autorizado | | `403` | `insufficient_scope` (escopo insuficiente) ou `account_locked` (conta em modo só leitura) | | `404` | não existe **ou** é de outro tenant (indistinguível de propósito) | | `409` | conflito de estado (ex.: não há fatura aberta, assinatura fora da janela de cobrança) | | `422` | regra de negócio (ex.: troca de plano inválida, telefone inválido) | | `429` | limite de requisições da rota excedido | | `202` | aceito e enfileirado (ex.: cobrar agora) | ## Idempotência A ribbo é **idempotente por semântica**, não por header: - As consultas (`GET`) são seguras de repetir. - `charge-now` e o estorno têm anti-duplicidade embutido. - Webhooks trazem `X-Billing-Event-Id` para você **deduplicar** (a entrega é *at-least-once*). ## Limites de uso (rate limit) Cada rota tem o seu próprio limite por minuto. Ao exceder, você recebe **429**. As rotas de **leitura** são mais folgadas; as que **movem dinheiro** (estornar, cobrar, trocar plano) são mais restritas. Um `429` **não cobra nada**: faça *backoff* e tente de novo. Para varrer muitos registros, use a paginação por cursor em vez de repetir a mesma chamada em rajada. --- # Assinaturas > **A assinatura nasce no checkout da ribbo.** O cliente assina pela página de checkout > (o número do cartão nunca toca o seu servidor nem o da ribbo). A API de integração serve > para **ler e gerenciar** as assinaturas, não para criá-las. ## Ler o estado atual de uma assinatura Qualquer status (inclusive `canceled`/`suspended`), útil para reconciliar um webhook perdido. Escopo `read`. ```bash curl "https://api.ribbo.app/v1/subscriptions/sub_01J…" -H "Authorization: Bearer bk_..." ``` O shape é **o mesmo do payload de webhook** ([Webhooks](/docs/webhooks)): `subscription_id`, `status`, `customer`, `plan_id`, `plan_name`, `interval`, `current_period_end`, `cancel_at_period_end`, `collection_method`, `payment_method`, `next_charge_amount_cents`, `currency`, `pending_offer`, `entitlements`, `last_payment` e `tracking`. ## Listar assinaturas do tenant ```bash curl "https://api.ribbo.app/v1/subscriptions?status=active,past_due&limit=100" -H "Authorization: Bearer bk_..." ``` Filtros: `?status=` (CSV; valor desconhecido dá **400**), `?offer_id=`, `?limit=` (máx 500), `?after=` (cursor). Resposta `{results, next}`. Nas listas, `last_payment` e `tracking` são omitidos (leia a assinatura pelo id se precisar deles). ## Assinaturas de um cliente ```bash curl "https://api.ribbo.app/v1/customers/cliente-123/subscriptions" -H "Authorization: Bearer bk_..." ``` ## Estados (status) `trialing`, `active`, `past_due` (inadimplente, em retentativa), `suspended`, `canceled`, `pending_first_payment`. **O acesso do cliente é decidido pelo `status`**: ver a regra de ouro em [Webhooks](/docs/webhooks). ## Teste grátis (trial) Durante o teste a assinatura fica `trialing` (evento `subscription.trial_started`). No fim do teste a ribbo cobra o cartão salvo: aprovado, a assinatura vira `active` e chega `invoice.paid`; recusado, ela vira `past_due` e entra na régua abaixo. Quem cancela durante o teste (`cancel_at_period_end: true`) não é cobrado. ## Régua de cobrança - **Cartão recusado:** novas tentativas em **D+1, D+3, D+5 e D+9**, contados do vencimento. O acesso continua durante o atraso (`past_due`); depois da última tentativa a assinatura fica `suspended` e volta para `active` quando o cliente paga. - **Pix na renovação:** o QR é enviado 3 dias antes do vencimento, com lembretes 1 e 3 dias depois. Sem pagamento, o acesso é cortado no mesmo D+9; o QR segue pagável até D+15. --- # Troca de plano Escopo `write`. Troca a oferta de uma assinatura. ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/change-plan" \ -H "Authorization: Bearer bk_..." -H "Content-Type: application/json" \ -d '{ "offer_id": "offer_01KZ9R...", "keep_coupon": true }' ``` - **Mesmo ciclo, mais caro (upgrade):** entra na hora, com **proração cobrada no cartão salvo**. Se a proração fica abaixo do mínimo do gateway (Stripe R$ 0,50, Asaas R$ 5,00), a troca é aplicada sem cobrar. - **Mesmo ciclo, mesmo preço:** troca na hora, sem cobrança. - **Mesmo ciclo, mais barato (downgrade):** **agendado** para o fim do período atual. - **Troca de ciclo** (mensal↔anual): vale o preço por ano (mensal x 12). Se o novo plano custa mais por ano, a troca é na hora: de mensal para anual cobra o anual menos o crédito do período que sobra; de anual para mensal o crédito vira tempo. Se custa o mesmo ou menos por ano, é **agendada** para o fim do período. - Oferta de **outro gateway**: o cartão salvo não atravessa gateways, então a troca vira um link de pagamento no gateway novo (evento `subscription.migration_link_issued` quando é agendada). - Só assinaturas `active` ou `trialing` trocam de plano. - `keep_coupon` (opcional): leva o cupom ativo para a nova oferta (não vale com troca de gateway). Resposta `200` `{ "subscription": {...}, "plan_change": {...} }`. Sem `offer_id` → **400**; regra de negócio inválida → **422** `{"error":"..."}`. A troca agendada aparece em `pending_offer` (`offer_id`, `plan_id`, `plan_name`, `interval`, `amount_cents`, `effective_at`) no payload da assinatura e no webhook `subscription.plan_change_scheduled`. Quando entra em vigor, chega `subscription.plan_changed`. ## Cancelar troca agendada ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/cancel-plan-change" -H "Authorization: Bearer bk_..." ``` **422** se não houver troca agendada. Emite `subscription.plan_change_canceled`. --- # Ações de cobrança Todas escopo `write`. ## Estornar um pagamento Total ou parcial. `amount_cents` e `reason` obrigatórios. ```bash curl -X POST "https://api.ribbo.app/v1/payments/pay_01J…/refund" \ -H "Authorization: Bearer bk_..." -H "Content-Type: application/json" \ -d '{ "amount_cents": 1990, "reason": "cliente desistiu", "revoke": "none" }' ``` - `revoke`: `subscription` (cancela a assinatura) | `grant` (revoga a concessão do pedido) | `none` (só devolve o dinheiro). **Se omitido, revoga o acesso apropriado**: fatura de assinatura cancela a assinatura; pedido avulso revoga a concessão. Mande `"revoke": "none"` para não revogar. - Se o estorno desfaz uma extensão/renovação, o tempo concedido é revertido automaticamente. - Se o estorno atinge uma fatura que financiou *runway* pré-pago, a resposta é **422** com `{"requires_confirmation":"runway"}`. Reenvie com `"confirm_runway": true` para confirmar. - Anti-duplo-estorno embutido. Emite `payment.refunded`. ## Cancelar assinatura ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/cancel" \ -H "Authorization: Bearer bk_..." -H "Content-Type: application/json" \ -d '{ "reason": "a pedido", "at_period_end": true }' ``` - **Imediato** (padrão, sem `at_period_end`): terminal e idempotente. **422** se houver um QR Pix ainda pagável. Emite `subscription.canceled`. - **No fim do período** (`"at_period_end": true`): o acesso continua até o fim do período pago e a assinatura não renova (`cancel_at_period_end: true`). É reversível: ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/cancel/undo" -H "Authorization: Bearer bk_..." ``` **422** se não há cancelamento agendado. Emite `subscription.updated`. ## Cobrar agora Enfileira a cobrança do ciclo no cartão salvo. Resposta **202** `{ "ok": true, "message": "..." }`. A tarefa respeita a data da cobrança: só cobra quando a próxima cobrança já venceu (ex.: uma renovação que ficou para trás). Se a data ainda não chegou, nada é cobrado; para receber antes do vencimento, use o [link de renovação antecipada](/docs/payment-links). ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/charge-now" -H "Authorization: Bearer bk_..." ``` **409** se a assinatura não está em `active`, `trialing` ou `past_due` (o corpo traz o `status`) ou se as renovações da conta estão pausadas. Chamar duas vezes não cobra duas vezes. > A API cobra direto no cartão salvo. O painel da ribbo, ao contrário, nunca cobra o cartão: lá > a cobrança manual e a troca de plano com cobrança geram um link de pagamento para o cliente. ## Reagendar a próxima cobrança Não cobra, só move a data. `next_billing_at` deve ser futuro (ISO-8601). ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/reschedule" \ -H "Authorization: Bearer bk_..." -H "Content-Type: application/json" \ -d '{ "next_billing_at": "2026-10-01T12:00:00Z" }' ``` Emite `subscription.updated`. Erros trazem `code`: **400** `data_invalida`/`data_passada`, **422** `status` (status não permite), **409** `fatura_em_voo` (cobrança ou QR Pix em aberto). ## Remover cupom Tira o cupom dos ciclos futuros. Resposta `{ "removed": }`. ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/remove-coupon" -H "Authorization: Bearer bk_..." ``` --- # Links de pagamento Links opacos, não enumeráveis, que você entrega ao cliente. Nunca expõem IDs internos. Se o cadastro do cliente não tem celular, a página pública pede um antes de cobrar. ## Link da fatura em aberto (leitura) ```bash curl "https://api.ribbo.app/v1/subscriptions/sub_01J…/payment-link" -H "Authorization: Bearer bk_..." ``` Resposta `{ "token", "url", "amount_cents", "status" }`. **409** se não há fatura em aberto. ## 2ª via do Pix de um pedido (leitura) Para uma compra avulsa ou um adiantamento de renovação com Pix pendente. ```bash curl "https://api.ribbo.app/v1/orders/ord_01J…/payment-link" -H "Authorization: Bearer bk_..." ``` Resposta `{ "token", "url", "amount_cents", "status" }`. **409** se não há Pix pendente para reabrir. ## Link de renovação antecipada (escrita) Gera um documento pagável que, pago, **estende** o período da assinatura. Expira em 7 dias. Renova sempre a **própria oferta** da assinatura: `offer_id` é opcional e, se enviado, tem de ser a oferta atual (para mudar de plano use a [troca de plano](/docs/change-plan)). ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/renewal-link" \ -H "Authorization: Bearer bk_..." -H "Content-Type: application/json" -d '{}' ``` Resposta **201** `{ "token", "url", "price_cents", "expires_at", "status" }`. Sem `price_cents`, o preço é o do próximo ciclo já com o cupom ativo; com `price_cents` (inteiro >= 1) você fixa outro valor. **400** para `price_cents` inválido; **422** para uma oferta diferente da atual. ## Link de troca de forma de pagamento (escrita) Para o cliente trocar de cartão ou de Pix↔cartão, **sem cobrança**. Single-use, expira em 7 dias. ```bash curl -X POST "https://api.ribbo.app/v1/subscriptions/sub_01J…/payment-method-link" -H "Authorization: Bearer bk_..." ``` Resposta **201** `{ "token", "url", "expires_at", "status" }`. **409** se a assinatura está `canceled`, `pending_first_payment` ou em teste sem cartão. --- # Clientes ## Atualizar e-mail Sincroniza local + gateway e registra auditoria. `external_id` é o **seu** ID. ```bash curl -X POST "https://api.ribbo.app/v1/customers/cliente-123/email" \ -H "Authorization: Bearer bk_..." -H "Content-Type: application/json" \ -d '{ "email": "novo@empresa.com" }' ``` **422** se o e-mail é inválido ou já é de outro cliente do tenant. ## Atualizar nome / e-mail / telefone Só os campos enviados mudam (`null` ou ausente = não mexe). ```bash curl -X POST "https://api.ribbo.app/v1/customers/cliente-123" \ -H "Authorization: Bearer bk_..." -H "Content-Type: application/json" \ -d '{ "name": "Novo Nome", "phone": "+5511999999999" }' ``` - O cadastro precisa terminar com um **celular válido**, em formato internacional **E.164** (`+5511999999999`; um número brasileiro sem `+` vira `+55`). Os gateways podem exigir o telefone na cobrança, e o novo número é sincronizado com eles. - Cliente antigo sem telefone: qualquer edição exige informar um. - **422** com `{"error": "telefone inválido"}`, `"informe o celular do cliente"` ou e-mail inválido/em uso. - `doc` e `external_id` **não** são editáveis. ## Histórico de pagamentos Recorrente + avulso, com cursor. ```bash curl "https://api.ribbo.app/v1/customers/cliente-123/payments?limit=50" -H "Authorization: Bearer bk_..." ``` Cada item traz `payment_id`, `status`, `kind` (`recurring`/`one_time`), `method`, `amount_cents`, `refunded_amount_cents`, `failure_code` (motivo da recusa), `provider` (`pagarme`, `stripe` ou `asaas`), `card` mascarado e `reference`. Detalhe de um pagamento (com a decomposição `items`: plano/bump/juros): ```bash curl "https://api.ribbo.app/v1/payments/pay_01J…" -H "Authorization: Bearer bk_..." ``` --- # Entitlements *Entitlements* são as **capacidades** que um plano concede, o que o seu SaaS deve liberar (`{"seats": 5, "api": true}`, etc.). A ribbo não conhece "planos" do seu lado, só capacidades. ## Consultar as capacidades de um cliente ```bash curl "https://api.ribbo.app/v1/entitlements?external_id=cliente-123" -H "Authorization: Bearer bk_..." # ou ?email=cliente@empresa.com ``` Lista as assinaturas do cliente que **não estão canceladas**, cada uma com os entitlements do plano, e soma em `merged_entitlements` as que estão `active`, `trialing` ou `past_due` (uma `suspended` aparece na lista, mas não soma): ```json { "customer_id": "cus_01J…", "subscriptions": [ { "id": "sub_01J…", "status": "active", "plan_id": "plan_01J…", "interval": "month", "current_period_end": "2026-09-01T00:00:00Z", "entitlements": { "seats": 5, "api": true } } ], "merged_entitlements": { "seats": 5, "api": true } } ``` Cliente inexistente → **200** com `customer_id: null` (nunca 404). Sem `external_id` nem `email` → **400**. ## Editar o plano reflete sozinho Os entitlements vêm sempre do catálogo atual. Ao editar o que um plano libera no painel, a ribbo envia `subscription.updated` (com o mapa novo) para **cada assinatura viva** daquele plano, e o `GET /v1/entitlements` já devolve o novo conjunto. O seu app não precisa de script de migração. ## 🔑 A regra de ouro No **webhook**, o campo `entitlements` vem **cheio mesmo quando a assinatura está `suspended`/`past_due`/`canceled`**. **Decida o acesso pelo `status`, nunca pela presença de `entitlements`**, senão você re-libera acesso a um inadimplente. Ver [Webhooks](/docs/webhooks). O `merged_entitlements` acima já segue o status (só soma `active`, `trialing` e `past_due`) e serve para reconciliar quando o seu app perdeu um webhook. --- # Indicações e campanhas ## Link de indicação de um cliente Get-ou-create do código do indicador + recompensa + progresso. Escopo `read`. ```bash curl "https://api.ribbo.app/v1/referrals/link?external_id=cliente-123" -H "Authorization: Bearer bk_..." # ou ?email= ``` Resposta: ```json { "active": true, "code": "ABC123", "token": "…", "link": "https://…", "reward": { "kind": "…", "value": 0, "cycles": 1, "currency": "brl", "label": "…" }, "progress": { "total": 0, "qualified": 0, "rewarded": 0, "reverted": 0 } } ``` `{"active": false}` se não há programa de indicação. ## Campanhas de renovação Gerar (ou obter, idempotente) o link de renovação de um cliente do público-alvo. Escopo `write` (gera um documento pagável), mesmo sendo um `GET`. ```bash curl "https://api.ribbo.app/v1/renewal-campaigns/rnc_01J…/link?external_id=cliente-123" -H "Authorization: Bearer bk_..." ``` Resposta `{ "token", "url", "price_cents", "bonus_cycles", "bonus_unit", "status", "starts_at", "offer_id", "plan_id", "product_id" }`. `starts_at` no futuro = o link só é pagável a partir dessa data. **404** se o cliente não está no público da campanha. Listar todos os links da campanha (cursor, escopo `read`): ```bash curl "https://api.ribbo.app/v1/renewal-campaigns/rnc_01J…/links?limit=100" -H "Authorization: Bearer bk_..." ``` --- # Webhooks A ribbo envia um `POST` assinado ao seu endpoint a cada evento. Cadastre a URL e escolha os eventos no painel (**Configurações → Webhooks**). ## Envelope ```json { "id": "evt_01J…", "type": "invoice.paid", "created_at": "2026-08-01T12:00:00Z", "tenant_id": "tn_01J…", "data": { "...": "payload específico do tipo" } } ``` ## Os 19 eventos | Evento | Quando | |---|---| | `subscription.activated` | assinatura ativada (1ª cobrança aprovada no checkout, ou reativação) | | `subscription.updated` | re-sync do estado (ex.: reagendamento, troca de forma de pagamento, cupom removido, entitlements do plano editados, cancelamento agendado ou desfeito) | | `subscription.plan_changed` | troca de plano efetivada | | `subscription.plan_change_scheduled` | troca de plano agendada | | `subscription.plan_change_canceled` | troca de plano agendada cancelada | | `subscription.migration_link_issued` | troca agendada para uma oferta de outro gateway venceu e virou link de pagamento | | `subscription.trial_started` | trial iniciado | | `subscription.past_due` | assinatura inadimplente (em retentativa) | | `subscription.suspended` | assinatura suspensa (retentativas esgotadas) | | `subscription.canceled` | assinatura cancelada | | `invoice.paid` | fatura paga (1ª cobrança ou renovação) | | `invoice.payment_failed` | falha no pagamento da fatura | | `order.paid` | pedido avulso pago | | `order.refunded` | pedido avulso estornado | | `payment.refunded` | pagamento estornado, total ou parcial (de assinatura ou de pedido) | | `referral.created` / `referral.qualified` / `referral.rewarded` / `referral.reverted` | ciclo de indicação | Os eventos `subscription.*` e `invoice.*` trazem o `data` de uma assinatura; os `order.*`, o de pedido; `payment.refunded` traz um ou outro, conforme o que foi estornado. Veja os campos exatos em [/docs/reference](/docs/reference). ## Campos do `data` de assinatura `subscription_id`, `status`, `customer` (`id`, `email`, `external_id`), `plan_id`, `plan_name`, `interval`, `current_period_end`, `cancel_at_period_end`, `collection_method` (`card_auto`/`pix_manual`), `payment_method` (cartão mascarado ou `null`), `next_charge_amount_cents`, `currency`, `pending_offer` (troca agendada ou `null`), `entitlements`, e ainda: - `last_payment` (em `order.*`, `payment`): o pagamento que liquidou (método, `gateway_charge_id`, `gateway_provider`, `paid_at`, valor em centavos). No `invoice.paid` é o desta fatura; `null` em trial/sem cobrança. - `tracking`: UTMs e click-ids (`gclid`/`fbclid`/`fbc`) do link do checkout, e em `params` os demais parâmetros do link (ex.: `sck`, `src`), sem dados pessoais; `inherited: true` quando vem herdado da assinatura (renovação/upgrade). `null` sem contexto. O `data` de pedido traz `order_id`, `status`, `customer`, `plan_id`, `plan_name`, `quantity`, `amount_cents`, `unit_amount_cents`, `currency`, `paid_at`, `target_subscription_id`, `entitlements` e `grant_days` (crus, nunca multiplicados por `quantity`), `payment` e `tracking`. O `customer` traz só `id`/`email`/`external_id`: **doc, telefone e nome não cruzam** (minimização de PII). ## 🔑 A regra de ouro O campo `data.entitlements` vem **cheio mesmo em `suspended`/`past_due`/`canceled`**. **Decida o acesso do cliente por `data.status`, NUNCA pela presença de `entitlements`.** Um receptor que só "aplica os entitlements no perfil" **re-libera acesso a um inadimplente**. ## Validar a assinatura (obrigatório) Cada POST traz os headers: - `X-Billing-Event-Id: evt_…`: chave de **dedupe** (a entrega é *at-least-once*; trate idempotente). - `X-Billing-Signature: ts=,v1=`, onde `v1 = HMAC-SHA256(secret, ".")`. Valide em **tempo constante**, sobre o **corpo cru** (não o re-serializado), com o `secret` do endpoint (do painel). **Node.js:** ```js import crypto from "node:crypto"; function verify(rawBody, header, secret) { const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("="))); const expected = crypto.createHmac("sha256", secret) .update(`${parts.ts}.${rawBody}`).digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)); } ``` **Python:** ```python import hmac, hashlib def verify(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(kv.split("=", 1) for kv in header.split(",")) expected = hmac.new(secret.encode(), f"{parts['ts']}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts["v1"]) ``` ## Confirmação e retries Responda **2xx** para confirmar. Sem 2xx (ou timeout de 10s), a ribbo re-tenta: **1min → 5min → 30min → 2h → 6h → 24h** (6 retries, 7 tentativas no total). Depois marca como esgotado e alarma. Um endpoint com URL malformada é fatal (sem retry). ## Reconciliação Perdeu um evento? Releia o estado atual com `GET /v1/subscriptions/{id}` (qualquer status): é a mesma forma do payload, sempre atual. --- # Servidor MCP (assistentes de IA) MCP (*Model Context Protocol*) é o padrão que deixa um assistente de IA usar ferramentas externas. Com o servidor MCP da ribbo (pacote [`ribbo-mcp`](https://www.npmjs.com/package/ribbo-mcp) no npm), o seu assistente passa a consultar e operar a sua cobrança: ver assinaturas e pagamentos, conferir o que um cliente tem liberado, trocar plano, estornar, cobrar na hora e gerar links. Exemplos do que você pode pedir: - "Quais assinaturas estão em atraso hoje?" - "O cliente `user-123` tem acesso a quê agora?" - "Gere um link para o cliente trocar o cartão da assinatura `sub_...`." - "Estorne R$ 49,90 do pagamento `pay_...` sem cancelar a assinatura." ## Antes de começar - **Node.js 18 ou mais novo** (o cliente roda `npx -y ribbo-mcp`; não precisa clonar nada). - Uma **API key** criada em **Configurações → API keys**. Prefira uma chave `read` (só consultas); use `write` apenas se quiser que a IA aja (estornar, cobrar, cancelar, trocar plano). | Variável | Para que serve | |---|---| | `RIBBO_API_KEY` | Sua API key (`bk_...`). `read` libera só as consultas; `write` libera também as ações. | | `RIBBO_API_BASE` | Endereço da API, sem barra no fim. Padrão: `https://api.ribbo.app` | Nos métodos abaixo a chave **não fica escrita em arquivo do projeto**. Escolha o seu cliente, do mais fácil para o mais trabalhoso. ## 1. Claude Code **Opção A, um comando (só para você).** A chave fica na configuração local do Claude Code: ```bash claude mcp add ribbo --env RIBBO_API_KEY=bk_sua_chave --env RIBBO_API_BASE=https://api.ribbo.app -- npx -y ribbo-mcp ``` Confira com `claude mcp list` ou, dentro do Claude Code, com `/mcp`. **Opção B, na pasta do projeto (para o time).** Com `--scope project` o Claude Code grava um `.mcp.json` na raiz do repositório, que pode ir para o git. Nunca escreva a chave nele: use a expansão `${RIBBO_API_KEY}` (as aspas simples impedem o terminal de trocar pelo valor). ```bash claude mcp add ribbo --scope project --env 'RIBBO_API_KEY=${RIBBO_API_KEY}' --env RIBBO_API_BASE=https://api.ribbo.app -- npx -y ribbo-mcp ``` O `.mcp.json` resultante (pode criar à mão também): ```json { "mcpServers": { "ribbo": { "command": "npx", "args": ["-y", "ribbo-mcp"], "env": { "RIBBO_API_KEY": "${RIBBO_API_KEY}", "RIBBO_API_BASE": "https://api.ribbo.app" } } } } ``` Cada pessoa exporta a própria chave antes de abrir o Claude Code: ```bash # macOS / Linux export RIBBO_API_KEY=bk_sua_chave ``` ```powershell # Windows (PowerShell): só a sessão atual $env:RIBBO_API_KEY = "bk_sua_chave" # ou permanente para o seu usuário (abra um terminal novo depois) setx RIBBO_API_KEY "bk_sua_chave" ``` Na primeira vez o Claude Code pergunta se você confia no servidor do projeto. Aprove. ## 2. Cursor **Opção A, um clique.** Com `RIBBO_API_KEY` exportada (comandos acima), abra o link de instalação. Ele abre o Cursor e pede para confirmar: ``` cursor://anysphere.cursor-deeplink/mcp/install?name=ribbo&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInJpYmJvLW1jcCJdLCJlbnYiOnsiUklCQk9fQVBJX0tFWSI6IiR7ZW52OlJJQkJPX0FQSV9LRVl9IiwiUklCQk9fQVBJX0JBU0UiOiJodHRwczovL2FwaS5yaWJiby5hcHAifX0%3D ``` O `config` é esta configuração em base64: `{"command":"npx","args":["-y","ribbo-mcp"],"env":{"RIBBO_API_KEY":"${env:RIBBO_API_KEY}","RIBBO_API_BASE":"https://api.ribbo.app"}}` **Opção B, na pasta do projeto.** Crie `.cursor/mcp.json`; a chave é lida da variável de ambiente com `${env:RIBBO_API_KEY}`, então o arquivo pode ir para o git: ```json { "mcpServers": { "ribbo": { "command": "npx", "args": ["-y", "ribbo-mcp"], "env": { "RIBBO_API_KEY": "${env:RIBBO_API_KEY}", "RIBBO_API_BASE": "https://api.ribbo.app" } } } } ``` Exporte `RIBBO_API_KEY`, reabra o Cursor e confira na página **Customize** (barra lateral) se o `ribbo` está ativo. ## 3. VS Code (Copilot no modo agente) Crie `.vscode/mcp.json`. Com `inputs` e `"password": true`, o VS Code **pede a chave na primeira vez** e a guarda de forma segura, fora do arquivo: ```json { "inputs": [ { "type": "promptString", "id": "ribbo-api-key", "description": "API key da Ribbo (bk_...)", "password": true } ], "servers": { "ribbo": { "type": "stdio", "command": "npx", "args": ["-y", "ribbo-mcp"], "env": { "RIBBO_API_KEY": "${input:ribbo-api-key}", "RIBBO_API_BASE": "https://api.ribbo.app" } } } } ``` Abra o Chat (Ctrl+Alt+I no Windows/Linux, Ctrl+Cmd+I no macOS), escolha o modo **Agent** e faça uma pergunta. O VS Code inicia o servidor, pede a chave e pede para você confiar nele. ## 4. Claude Desktop O Claude Desktop não lê variáveis do projeto, então a chave vai no arquivo de configuração do app (na sua máquina, fora de qualquer repositório). 1. No Claude Desktop, vá em **Settings → Developer → Edit Config** (abre o `claude_desktop_config.json`). 2. Cole o bloco abaixo (se já houver `mcpServers`, acrescente só a entrada `ribbo`): ```json { "mcpServers": { "ribbo": { "command": "npx", "args": ["-y", "ribbo-mcp"], "env": { "RIBBO_API_KEY": "bk_sua_chave_aqui", "RIBBO_API_BASE": "https://api.ribbo.app" } } } } ``` 3. Salve, **feche o Claude Desktop por completo** e abra de novo. 4. As ferramentas da ribbo aparecem no ícone de ferramentas da caixa de mensagem. ## Ferramentas Leitura (chave `read`): `check_entitlements`, `list_subscriptions`, `get_subscription`, `get_gateway_events`, `get_payment`, `get_customer_subscriptions`, `get_customer_payments`, `get_referral_link`, `list_renewal_campaign_links`, `get_payment_link`, `get_order_payment_link`. Escrita (chave `write`): `change_plan`, `cancel_plan_change`, `cancel_subscription` (na hora ou com `at_period_end`), `undo_cancel_subscription`, `charge_now`, `reschedule_subscription`, `remove_coupon`, `create_renewal_link`, `create_payment_method_link`, `get_renewal_campaign_link`, `refund_payment`, `update_customer`, `update_customer_email`. Não há ferramenta para criar assinatura: ela nasce só no checkout. --- # Testes e sandbox - Use uma **API key de escopo `read`** para explorar sem risco de mover dinheiro. - A **referência** ([/docs/reference](/docs/reference)) lista cada chamada com parâmetros, corpo, respostas e exemplos. Para testar, use os `curl` dos guias com a sua chave, ou importe o [`/openapi.json`](/openapi.json) no seu cliente HTTP (Postman, Insomnia, Bruno). - As consultas (`GET`) são seguras de repetir; as ações de dinheiro têm anti-duplicidade embutido. - Para receber webhooks em desenvolvimento, exponha o seu endpoint local com um túnel (ex.: um serviço de tunneling) e cadastre a URL em **Configurações → Webhooks**. - **Nunca** logue o corpo do webhook com dados sensíveis nem a sua API key. ## Contrato legível por máquina - OpenAPI 3.1: [`/openapi.json`](/openapi.json) - Índice para IA: [`/llms.txt`](/llms.txt) · doc completa: [`/llms-full.txt`](/llms-full.txt) - Servidor MCP: [guia de instalação](/docs/mcp) --- # Referência de endpoints - POST /v1/customers/{external_id}: Atualizar o cadastro de um cliente (nome, e-mail, telefone) - POST /v1/customers/{external_id}/email: Atualizar o e-mail de um cliente - GET /v1/customers/{external_id}/payments: Histórico de pagamentos de um cliente — keyset paginado - GET /v1/customers/{external_id}/subscriptions: Listar as assinaturas de um cliente - GET /v1/entitlements: Entitlements de um cliente (o que ele pode usar AGORA) - GET /v1/orders/{order_id}/payment-link: Link de re-acesso ao Pix de um pedido (avulso ou adiantamento) - GET /v1/payments/{payment_id}: Detalhe de um pagamento (com itens) - POST /v1/payments/{payment_id}/refund: Estornar um pagamento (total ou parcial) - GET /v1/referrals/link: Link/código de indicação de um cliente + progresso - GET /v1/renewal-campaigns/{campaign_id}/link: Link de renovação de UM cliente (botão da plataforma) - GET /v1/renewal-campaigns/{campaign_id}/links: Todos os links da campanha (lote, para e-mailar) — keyset paginado - GET /v1/subscriptions: Listar as assinaturas do tenant — keyset paginado - GET /v1/subscriptions/{subscription_id}: Detalhe de UMA assinatura (estado atual, qualquer status) - POST /v1/subscriptions/{subscription_id}/cancel: Cancelar uma assinatura (imediato ou no fim do período) - POST /v1/subscriptions/{subscription_id}/cancel-plan-change: Cancelar uma troca de plano AGENDADA - POST /v1/subscriptions/{subscription_id}/cancel/undo: Desfazer um cancelamento agendado - POST /v1/subscriptions/{subscription_id}/change-plan: Trocar o plano de uma assinatura (upgrade/downgrade) - POST /v1/subscriptions/{subscription_id}/charge-now: Cobrar agora (enfileira o billing run da assinatura) - GET /v1/subscriptions/{subscription_id}/gateway-events: Timeline do gateway de uma assinatura (auditoria) - GET /v1/subscriptions/{subscription_id}/payment-link: Link de pagamento da fatura em aberto de uma assinatura - POST /v1/subscriptions/{subscription_id}/payment-method-link: Gerar link de troca de forma de pagamento - POST /v1/subscriptions/{subscription_id}/remove-coupon: Remover o cupom de uma assinatura - POST /v1/subscriptions/{subscription_id}/renewal-link: Gerar link de renovação avulsa de uma assinatura - POST /v1/subscriptions/{subscription_id}/reschedule: Reagendar a data da próxima cobrança de uma assinatura # Webhooks - invoice.paid: Fatura paga - invoice.payment_failed: Falha no pagamento da fatura - order.paid: Pedido pago - order.refunded: Pedido estornado - payment.refunded: Pagamento estornado - referral.created: Indicação criada - referral.qualified: Indicação qualificada - referral.reverted: Recompensa de indicação revertida - referral.rewarded: Recompensa de indicação concedida - subscription.activated: Assinatura ativada - subscription.canceled: Assinatura cancelada - subscription.migration_link_issued: Link de migração de gateway emitido - subscription.past_due: Assinatura inadimplente - subscription.plan_change_canceled: Troca de plano cancelada - subscription.plan_change_scheduled: Troca de plano agendada - subscription.plan_changed: Plano alterado - subscription.suspended: Assinatura suspensa - subscription.trial_started: Trial iniciado - subscription.updated: Assinatura atualizada