Quando você bate o olho em uma rota de API, o que você vê?
Se for PATCH /subscriptions/:id, você vê um recurso e um verbo. O que está sendo atualizado? Qual campo? Qual efeito vai acontecer? Para saber, você precisa da documentação, do schema do corpo, da descrição dos campos. A rota em si não te conta nada.
É esse gap — entre o que a rota diz e o que ela faz — que me incomodava quando tínhamos que integrar com bancos parceiros.
O que REST não te conta
REST é um padrão excelente. Ele resolveu um problema real e segue sendo a espinha dorsal de quase toda API que existe. Não tenho nada contra ele.
O problema não é o protocolo. O problema é quando o contrato da sua API é o verbo HTTP.
GET, POST, PUT, PATCH, DELETE — esses verbos descrevem o mecanismo. Eles não descrevem a intenção. E quando você trabalha com pagamentos, onde cada operação tem regras específicas, estados bem definidos e consequências reais, a intenção importa muito.
Cancelar uma assinatura não é deletar um recurso. Mudar de plano não é um patch genérico. Registrar consumo de uma feature metered não é uma atualização de campo. São eventos de domínio com semântica própria — e esconder tudo isso atrás de PATCH /subscriptions/:id é, no mínimo, impreciso.
Como a AbacatePay trata isso
Quando projetamos a API, tomamos uma decisão deliberada: cada endpoint deveria expressar a intenção de quem está chamando, não o verbo HTTP subjacente.
Para as assinaturas, ficou assim:
POST /subscriptions/cancel
POST /subscriptions/change-plan
POST /subscriptions/record-usageVocê lê e sabe. Sem documentação, sem lookup no schema, sem inferência. O /cancel cancela. O /change-plan troca o plano. O /record-usage registra consumo.
Isso é o que chamamos de API baseada em intenção.
Stripe como maior exemplo
Stripe é, com razão, a referência quando se fala em qualidade de API. Então vale a comparação direta.
Para as mesmas três operações que temos nas assinaturas da AbacatePay, o modelo REST convencional ficaria assim:
Cancelar uma assinatura
# REST convencional (como Stripe fazia)
DELETE /v1/subscriptions/:id
# AbacatePay (intent-based)
POST /subscriptions/cancel
{ "id": "sub_xxx" }DELETE sugere que o recurso vai desaparecer. Na prática, a assinatura passa para o status CANCELLED — ela ainda existe, ainda é auditável, ainda aparece no histórico. O nome do endpoint implica algo que não acontece.
Vale notar: o própria Stripe percebeu isso e passou a adotar endpoints de ação explícitos em recursos mais novos, como POST /v1/subscriptions/:id/resume. A tendência confirma o que a experiência mostrou — quando a operação é complexa o suficiente, o verbo HTTP sozinho não basta.
Trocar de plano
# REST convencional
POST /v1/subscriptions/:id
{ "items": [{ "id": "si_xxx", "price": "price_novo" }] }
# AbacatePay (intent-based)
POST /subscriptions/change-plan
{ "id": "sub_xxx", "productId": "prod_novo", "quantity": 1 }No modelo REST, a troca de plano é um POST no recurso de assinatura modificando um item interno. Para saber que a mudança é agendada para o próximo ciclo — e não imediata — você precisa ler a documentação. /change-plan carrega essa semântica no próprio nome: é uma troca, não uma sobrescrita.
Registrar consumo
# REST convencional
POST /v1/subscription_items/:id/usage_records
{ "quantity": 100, "action": "increment" }
# AbacatePay (intent-based)
POST /subscriptions/record-usage
{ "id": "sub_xxx", "productId": "prod_xxx", "units": 100, "action": "add" }Aqui o contraste fica mais claro. No modelo REST, o consumo metered virou um sub-recurso de um item de assinatura — você precisa saber o ID do item, não da assinatura. É funcional, mas adiciona uma camada de abstração que não existe no domínio do negócio. Você está registrando o uso de um produto dentro de uma assinatura. O endpoint da AbacatePay diz exatamente isso.
Por que isso importa na prática
Para o desenvolvedor que integra
A rota é o contrato. Quando o nome da rota descreve a intenção, você consegue navegar pela API só lendo a lista de endpoints — sem precisar abrir cada um. Isso reduz o tempo de onboarding e diminui o risco de chamar a operação errada.
Para SDKs
Quando os endpoints têm nomes de intenção, os métodos do SDK se escrevem praticamente sozinhos:
await abacatepay.subscriptions.cancel({ id });
await abacatepay.subscriptions.changePlan({ id, productId, quantity });
await abacatepay.subscriptions.recordUsage({ id, productId, units, action });Não tem ambiguidade. Não tem um update() que faz dez coisas diferentes dependendo do payload. Cada método é uma ação — e o nome do método é a documentação mais básica que existe.
Para IA
Esse talvez seja o ponto mais relevante hoje.
Quando um modelo de linguagem está gerando código de integração — seja via Claude, Cursor, ou um agente autônomo —, o nome do endpoint é um dos sinais mais ricos disponíveis. POST /subscriptions/cancel é inequívoco. DELETE /subscriptions/:id exige que o modelo raciocine sobre o que “delete” significa nesse contexto: soft delete? hard delete? imediato? agendado? O modelo pode acertar, mas pode errar.
API com intenção clara é API que um agente consegue usar corretamente na primeira tentativa. Isso não é detalhe cosmético — é o que separa uma integração que funciona de uma que vai exigir correção manual.
É por isso que nos preocupamos tanto com isso. O MCP Server da AbacatePay, por exemplo, expõe ferramentas com nomes de ação diretos. Quando o nome da ferramenta já é a intenção, o agente não precisa inferir — ele só executa.
Isso não é contra o REST
Vale deixar claro: a AbacatePay usa REST onde REST faz sentido.
POST /checkouts/create, GET /customers/list, GET /subscriptions/get — esses são recursos com operações diretas. REST funciona muito bem para eles.
A questão é quando a operação é o ponto, não o recurso. Cancelar, mudar de plano, registrar uso — essas são ações de domínio que merecem endpoints com nomes de domínio. Não é uma revolução contra o protocolo, é uma decisão sobre onde a clareza importa mais do que a convenção.
Quando você lê POST /subscriptions/cancel e entende na hora o que acontece, isso não é uma coincidência de design. É uma escolha intencional de que a API deve trabalhar a favor de quem integra.
É assim que pensamos cada rota da AbacatePay.
Precisa de ajuda? ajuda@abacatepay.com
Construiu algo legal? Marca a gente. @abacatepay em todas as redes.