30 de abril de 2026

Como projetamos a nossa API para ser legível por humanos, SDKs e IA

Por que a AbacatePay usa endpoints baseados em intenção em vez de verbos REST puros — e como isso torna a API mais expressiva para humanos, SDKs e modelos de IA.

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-usage

Você 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.