Design de API pública para quem constrói um SaaS pela primeira vez: noções básicas
Design de API pública de forma prática para quem cria um SaaS pela primeira vez: escolha versionamento, paginação, limites de taxa, documentação e um SDK pequeno que você pode entregar rapidamente.

O problema real: entregar uma API que você consiga manter
Uma API pública não é apenas um endpoint que sua aplicação expõe. É uma promessa para pessoas fora do seu time de que o contrato continuará funcionando, mesmo enquanto você muda o produto.
A parte difícil não é escrever a v1. É mantê-la estável enquanto você corrige bugs, adiciona recursos e aprende o que os clientes realmente precisam.
Escolhas feitas cedo aparecem depois como chamados de suporte. Se respostas mudam de formato sem aviso, se a nomenclatura é inconsistente ou se os clientes não conseguem saber se uma requisição funcionou, você cria atrito. Esse atrito vira desconfiança, e desconfiança faz com que as pessoas parem de construir sobre você.
Velocidade também importa. A maioria dos construtores de SaaS pela primeira vez precisa entregar algo útil rápido e depois melhorar. O trade-off é simples: quanto mais rápido você entrega sem regras, mais tempo vai gastar desfazendo essas decisões quando usuários reais chegarem.
Bom o suficiente para v1 geralmente significa um conjunto pequeno de endpoints que mapeiam ações reais do usuário, nomes e formatos de resposta consistentes, uma estratégia clara de mudanças (mesmo que seja só v1), paginação previsível e limites sensatos, e docs que mostram exatamente o que enviar e o que receberá de volta.
Um exemplo concreto: imagine que um cliente cria uma integração que gera faturas todas as noites. Se você mais tarde renomear um campo, mudar formatos de data ou começar a retornar resultados parciais sem avisar, o job deles falha às 2h da manhã. Eles culpam sua API, não o código deles.
Se você constrói com uma ferramenta chat-driven como Koder.ai, é tentador gerar muitos endpoints rapidamente. Tudo bem, mas mantenha a superfície pública pequena. Você pode manter endpoints internos privados enquanto aprende o que deve fazer parte do contrato de longo prazo.
Comece com uma superfície de API pequena e clara
Bom design de API pública começa escolhendo um pequeno conjunto de substantivos (recursos) que combinem com a forma como os clientes falam do seu produto. Mantenha nomes de recursos estáveis mesmo se seu banco interno mudar. Ao adicionar recursos, prefira adicionar campos ou novos endpoints em vez de renomear recursos centrais.
Um conjunto prático inicial para muitos produtos SaaS inclui: users, organizations, projects e events. Se você não consegue explicar um recurso em uma frase, provavelmente não está pronto para ser público.
Mantenha o uso de HTTP monótono e previsível:
- GET lê dados (sem efeitos colaterais)
- POST cria algo (ou inicia uma ação)
- PATCH atualiza poucos campos
- DELETE remove ou desabilita algo
Auth não precisa ser sofisticada no primeiro dia. Se sua API é principalmente server-to-server (clientes chamando do backend), chaves de API costumam ser suficientes. Se clientes precisam agir como usuários finais individuais, ou você espera integrações de terceiros onde usuários concedem acesso, OAuth geralmente é uma escolha melhor. Escreva a decisão em linguagem clara: quem é o chamador e de quem são os dados que ele pode tocar?
Defina expectativas cedo. Seja explícito sobre o que é suportado vs. esforço melhor-que-pode-funcionar. Por exemplo: endpoints de listagem são estáveis e compatíveis com versões anteriores, mas filtros de busca podem se expandir e não são garantidos como exaustivos. Isso reduz tickets de suporte e te deixa livre para melhorar.
Se você está construindo em uma plataforma de vibe-coding como Koder.ai, trate a API como um contrato de produto: mantenha o contrato pequeno primeiro, depois cresça com base no uso real, não em suposições.
Versionamento sem se enredar
Versionamento é, em grande parte, sobre expectativas. Clientes querem saber: minha integração vai quebrar semana que vem? Você quer espaço para melhorar as coisas sem medo.
Versionamento na URL vs em header
Versionamento por header pode parecer limpo, mas é fácil de esconder em logs, caches e screenshots de suporte. Versionamento na URL é geralmente a escolha mais simples: /v1/.... Quando um cliente te envia uma requisição que falha, você vê a versão imediatamente. Também facilita rodar v1 e v2 lado a lado.
O que é uma mudança breaking, de verdade?
Uma mudança é breaking se um cliente bem-comportado pode parar de funcionar sem alterar seu código. Exemplos comuns:
- Renomear um campo (por exemplo
customer_idparacustomerId) - Mudar o tipo de um campo (string para number) ou seu significado
- Remover um endpoint ou um campo de resposta do qual clientes dependem
- Apertar regras de validação (antes opcional, agora obrigatório)
- Mudar requisitos de auth ou permissões padrão
Uma mudança segura é aquela que clientes antigos podem ignorar. Adicionar um novo campo opcional geralmente é seguro. Por exemplo, adicionar plan_name à resposta de GET /v1/subscriptions não quebra clientes que só leem status.
Uma regra prática: não remova nem repurpose campos dentro da mesma versão major. Adicione novos campos, mantenha os antigos e os aposente apenas quando estiver pronto para descontinuar a versão inteira.
Uma política de depreciação que você consegue seguir
Mantenha simples: anuncie depreciações cedo, retorne uma mensagem de aviso clara nas respostas e defina uma data final. Para uma primeira API, uma janela de 90 dias costuma ser realista. Durante esse período, mantenha v1 funcionando, publique uma nota de migração curta e certifique-se de que o suporte possa apontar para uma frase: v1 funciona até esta data; aqui está o que mudou na v2.
Se você constrói em uma plataforma como Koder.ai, trate versões de API como snapshots: envie melhorias em uma nova versão, mantenha a antiga estável e só a corte depois que os clientes tiverem tempo de migrar.
Padrões de paginação que permanecem previsíveis
Paginações são onde a confiança é ganha ou perdida. Se resultados pulam entre requisições, as pessoas param de confiar na sua API.
Use page/limit quando o conjunto de dados for pequeno, a consulta for simples e usuários frequentemente quiserem a página 3 de 20. Use paginação por cursor quando listas podem crescer muito, novos itens chegam frequentemente ou o usuário pode ordenar/filtrar bastante. Paginação por cursor mantém a sequência estável mesmo quando novos registros são adicionados.
Algumas regras mantêm a paginação confiável:
- Sempre defina uma ordenação padrão (por exemplo: created_at desc).
- Adicione um tie-breaker (por exemplo: id) para que a ordem seja determinística.
- Trate paginação como parte do contrato: mudar a ordenação depois é uma breaking change.
- Retorne o mínimo necessário para continuar: itens mais o próximo cursor (ou próxima página).
Totais são complicados. Um total_count pode ser caro em tabelas grandes, especialmente com filtros. Se você pode fornecer com baixo custo, inclua. Se não, omita ou torne opcional via flag de query.
Aqui estão formas simples de request/response.
// Page/limit
GET /v1/invoices?page=2\u0026limit=25\u0026sort=created_at_desc
{
"items": [{"id":"inv_1"},{"id":"inv_2"}],
"page": 2,
"limit": 25,
"total_count": 142
}
// Cursor-based
GET /v1/invoices?limit=25\u0026cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDozMDowMFoiLCJpZCI6Imludl8xMDAifQ==
{
"items": [{"id":"inv_101"},{"id":"inv_102"}],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDoyNTowMFoiLCJpZCI6Imludl8xMjUifQ=="
}
Rate limits e retries amistosos
Limites de taxa são menos sobre ser estrito e mais sobre manter o serviço no ar. Eles protegem sua app de picos de tráfego, o banco de dados de queries caras feitas com muita frequência e sua conta de custos de surpresas. Um limite também é um contrato: clientes sabem qual é o uso normal.
Comece simples e ajuste depois. Escolha algo que cubra o uso típico com espaço para rajadas curtas, depois observe o tráfego real. Se você não tem dados ainda, um padrão seguro é um limite por chave de API como 60 requests por minuto mais uma pequena folga de burst. Se um endpoint é muito pesado (como busca ou exports), dê um limite mais estrito ou uma regra de custo separada em vez de punir todas as requisições.
Ao impor limites, facilite para os clientes fazerem a coisa certa. Retorne um 429 Too Many Requests e inclua alguns headers padrão:
X-RateLimit-Limit: o máximo permitido na janelaX-RateLimit-Remaining: quantos restamX-RateLimit-Reset: quando a janela reinicia (timestamp ou segundos)Retry-After: quanto esperar antes de tentar de novo
Clientes devem tratar 429 como uma condição normal, não um erro para combater. Um padrão de retry educado deixa ambos os lados felizes:
- Aguarde
Retry-Afterquando presente - Caso contrário, use backoff exponencial (por exemplo 1s, 2s, 4s)
- Adicione um pouco de aleatoriedade (jitter) para que muitos clientes não tentem ao mesmo tempo
- Faça um limite máximo no tempo de espera (por exemplo 30–60s)
Exemplo: se um cliente roda um sync noturno que bate muito na API, o job dele pode espalhar as requisições por um minuto e desacelerar automaticamente ao receber 429s em vez de falhar o processo todo.
Erros, códigos de status e escritas seguras
Se seus erros de API são difíceis de ler, os tickets de suporte se acumulam rápido. Escolha uma forma de erro e mantenha-a em todo lugar, incluindo 500s. Um padrão simples é: code, message, details e um request_id que o usuário pode colar em um chat de suporte.
Aqui está um formato pequeno e previsível:
{
"error": {
"code": "validation_error",
"message": "Some fields are invalid.",
"details": {
"fields": [
{"name": "email", "issue": "must be a valid email"},
{"name": "plan", "issue": "must be one of: free, pro, business"}
]
},
"request_id": "req_01HT..."
}
}
Use códigos HTTP do mesmo jeito sempre: 400 para input inválido, 401 quando auth estiver ausente ou inválida, 403 quando o usuário estiver autenticado mas não autorizado, 404 quando um recurso não for encontrado, 409 para conflitos (como valor único duplicado ou estado incorreto), 429 para limites de taxa e 500 para erros do servidor. Consistência é melhor que esperteza.
Torne erros de validação fáceis de consertar. Dicas por campo devem apontar para o nome exato do parâmetro que seus docs usam, não para uma coluna interna do banco. Se existe um requisito de formato (data, moeda, enum), diga o que você aceita e mostre um exemplo.
Retries são onde muitas APIs acidentalmente criam dados duplicados. Para ações POST importantes (pagamentos, criação de faturas, envio de e-mails), suporte chaves de idempotência para que clientes possam re-tentar em segurança.
- Aceite um header
Idempotency-Keyem endpoints POST selecionados. - Armazene a chave com o resultado por uma janela curta (por exemplo, 24 horas).
- Em uma chave repetida, retorne a mesma resposta da primeira requisição.
- Se a primeira requisição ainda estiver em execução, retorne uma resposta clara de tente novamente em vez de criar um segundo recurso.
Esse único header previne muitos casos dolorosos quando redes são instáveis ou clientes batem timeouts.
Cenário exemplo: uma pequena API de billing SaaS na prática
Imagine que você roda um SaaS simples com três objetos principais: projects, users e invoices. Um project tem muitos users, e cada project recebe faturas mensais. Clientes querem sincronizar faturas para a ferramenta contábil e mostrar billing básico dentro do próprio app.
Uma v1 limpa poderia ser assim:
GET /v1/projects/{project_id}
GET /v1/projects/{project_id}/invoices
POST /v1/projects/{project_id}/invoices
Agora uma mudança breaking acontece. Na v1, você armazena valores de fatura como inteiro em cents: amount_cents: 1299. Mais tarde, você precisa de multicurrency e decimais, então quer amount: "12.99" e currency: "USD". Se você sobrescrever o campo antigo, toda integração existente quebra. Versionamento evita o pânico: mantenha v1 estável, envie /v2/... com os novos campos e suporte ambos até os clientes migrarem.
Para listar invoices, use um formato de paginação previsível. Por exemplo:
GET /v1/projects/p_123/invoices?limit=50\u0026cursor=eyJpZCI6Imludl85OTkifQ==
200 OK
{
"data": [ {"id":"inv_1001"}, {"id":"inv_1000"} ],
"next_cursor": "eyJpZCI6Imludl8xMDAwIn0="
}
Um dia um cliente importa invoices em loop e atinge seu rate limit. Em vez de falhas aleatórias, ele recebe uma resposta clara:
429 Too Many RequestsRetry-After: 20- um corpo de erro pequeno como
{ "error": { "code": "rate_limited" } }
Do lado deles, o cliente pode pausar por 20 segundos e então continuar a partir do mesmo cursor sem rebaixar tudo ou criar invoices duplicadas.
Passo a passo: um plano simples de release para sua API v1
Um lançamento de v1 vai melhor quando você trata como um pequeno release de produto, não um monte de endpoints. O objetivo é simples: pessoas conseguem construir em cima e você consegue melhorar sem surpresas.
Um plano prático de 1 semana (mesmo para um time minúsculo)
Comece escrevendo uma página que explique para que sua API serve e para que não serve. Mantenha a superfície pequena o suficiente para explicar em voz alta em um minuto.
Use esta sequência e não avance até que cada etapa esteja boa o suficiente:
- Rascunhe um spec de página única que nomeie seus recursos centrais e os primeiros endpoints que você vai suportar (pense 5–10). Inclua o padrão de base URL, método de auth e headers obrigatórios.
- Para cada endpoint, escreva uma requisição realista e uma resposta realista. Use nomes de campo que planeja entregar. Esses exemplos viram suas primeiras docs e seus primeiros testes.
- Adicione uma seção curta de regras: como erros aparecem, como a paginação funciona (se houver endpoint de listagem), quais são os rate limits e quais campos são estáveis vs podem mudar.
- Faça um teste interno com um cliente falso. Finja que você é um cliente construindo uma integração em um novo repositório. Cronometre quanto tempo leva para chegar na primeira chamada bem-sucedida e anote onde houve confusão.
- Publique seu contrato v1: o que é seguro mudar (campos aditivos), o que exige nova versão, e quanto aviso você dará sobre mudanças breaking.
Se você usa um fluxo que gera código (por exemplo, usar Koder.ai para scaffold de endpoints e respostas), ainda faça o teste do cliente falso. Código gerado pode parecer correto e ainda assim ser desconfortável de usar.
O ganho é menos emails de suporte, menos releases de hotfix e uma v1 que você realmente consegue manter.
Enviando um SDK pequeno sem superengenharia
Um primeiro SDK não é um segundo produto. Pense nele como um wrapper fino e amigável em torno da sua API HTTP. Deve facilitar as chamadas comuns, mas não esconder como a API funciona. Se alguém precisar de um recurso que você ainda não empacotou, ele deve conseguir usar requisições cruas.
Escolha uma linguagem para começar, baseada no que seus clientes realmente usam. Para muitos SaaS B2B, isso costuma ser JavaScript/TypeScript ou Python. Lançar um SDK sólido em uma linguagem vence lançar três pela metade.
O que um SDK pequeno deve incluir
Um bom conjunto inicial é:
- Tratamento de auth (chave de API ou token OAuth) em um lugar
- Timeouts sensatos e retries automáticos para requisições seguras (GET), com backoff
- Helpers de paginação que retornam um iterador ou função next
- Modelos claros de request/response (mesmo que tipos básicos)
- Uma saída para headers customizados e chamadas HTTP cruas
Você pode construir isso à mão ou gerar a partir de um OpenAPI spec. Geração é ótima quando seu spec é preciso e você quer tipagem consistente, mas frequentemente produz muito código. No início, um cliente minimalista escrito à mão mais um arquivo OpenAPI para docs geralmente é suficiente. Você pode trocar para clientes gerados depois sem quebrar usuários, contanto que a interface pública do SDK permaneça estável.
Versione o SDK separadamente da API
Sua versão de API deve seguir suas regras de compatibilidade. A versão do SDK deve seguir regras de empacotamento.
Se você adicionar parâmetros opcionais ou novos endpoints, isso geralmente é um bump menor no SDK. Reserve releases major do SDK para breaking changes no próprio SDK (renomear métodos, mudar defaults), mesmo que a API tenha permanecido a mesma. Essa separação mantém as atualizações calmas e os tickets de suporte baixos.
Erros comuns que geram dor de cabeça no suporte
A maioria dos tickets de API não é sobre bugs. É sobre surpresas. Design de API pública é, em grande parte, ser monótono e previsível para que o código cliente continue funcionando mês após mês.
A maneira mais rápida de perder confiança é mudar respostas sem avisar. Se você renomear um campo, mudar um tipo ou começar a retornar null onde antes havia um valor, você quebra clientes de formas difíceis de diagnosticar. Se realmente precisa mudar comportamento, versionize ou adicione um novo campo e mantenha o antigo por um tempo com um plano de sunset claro.
Paginação é outro reincidente. Problemas aparecem quando um endpoint usa page/pageSize, outro usa offset/limit e um terceiro usa cursors, todos com defaults diferentes. Escolha um padrão para v1 e use-o em todo lugar. Mantenha a ordenação estável também, para que a próxima página não pule ou repita itens quando novos registros aparecem.
Erros geram muito vai-e-vem quando são inconsistentes. Um modo comum de falha é um serviço retornar { "error":"..." } e outro { "message":"..." }, com códigos HTTP diferentes para o mesmo problema. Clientes então constroem handlers sujos específicos por endpoint.
Aqui estão cinco erros que geram os maiores threads de email:
- Mudanças silenciosas de campos (nomes, tipos, significado) sem versionamento ou janela de depreciação
- Regras de paginação que variam por endpoint ou mudam defaults no tempo
- Formatos de erro, códigos de status ou mensagens de validação diferentes entre endpoints
- Falta de request IDs, então nenhum dos lados encontra rapidamente a chamada falha nos logs
- Rate limits que disparam de repente, sem headers, sem orientação clara de retry e sem exemplo de backoff
Um hábito simples ajuda: toda resposta deve incluir um request_id, e todo 429 deve explicar quando tentar de novo.
Checklist rápido e próximos passos
Antes de publicar, faça uma passada final focada em consistência. A maioria dos tickets de suporte acontece porque pequenos detalhes não batem entre endpoints, docs e exemplos.
Verificações rápidas que pegam a maioria dos problemas:
- Naming: substantivos consistentes, recursos no plural e casing de campos (escolha um e mantenha)
- Exemplos: todo endpoint tem um request e response realistas, incluindo paginação
- Erros: uma forma clara de erro, códigos estáveis e mensagens úteis para falhas comuns
- Limites: comportamento de rate limit documentado e respostas contendo os headers esperados
- Segurança: idempotência para retries em creates e manejo previsível de timeouts
Depois do lançamento, observe o que as pessoas realmente usam, não o que você esperava. Um pequeno dashboard e uma revisão semanal são suficientes no início.
Monitore estes sinais primeiro:
- Picos de 429 (quem está sendo limitado e por quê)
- p95 de latência por endpoint (endpoints lentos muitas vezes escondem N+1 queries)
- Endpoints e parâmetros mais usados (sua superfície real de API)
- Taxas de erro por código de status (400 vs 401 vs 500)
- Timeouts e retries (clientes podem estar em loop sem perceber)
Colete feedback sem reescrever tudo. Adicione um caminho curto para reportar um problema nas docs e marque cada relato com endpoint, request id e versão do cliente. Quando você corrigir algo, prefira mudanças aditivas: novos campos, novos params opcionais ou um novo endpoint, em vez de quebrar o comportamento existente.
Próximos passos: escreva um spec de uma página com seus recursos, plano de versionamento, regras de paginação e formato de erro. Depois produza docs e um SDK inicial que cubra autenticação mais 2–3 endpoints centrais. Se quiser acelerar, você pode rascunhar spec, docs e um SDK inicial a partir de um plano chat-based usando ferramentas como Koder.ai (o modo de planejamento é uma forma prática de mapear endpoints e exemplos antes de gerar código).
Perguntas frequentes
Quantos endpoints devo expor na minha primeira API pública?
Comece com 5–10 endpoints que representem ações reais dos clientes.
Uma boa regra: se você não consegue explicar um recurso em uma frase (o que é, quem o possui, como é usado), mantenha-o privado até aprender mais pelo uso.
Quais recursos devo começar para uma API SaaS?
Escolha um pequeno conjunto de substantivos estáveis (recursos) que os clientes já usam ao falar do seu produto e mantenha esses nomes estáveis mesmo que seu banco de dados mude.
Iniciantes comuns em SaaS são users, organizations, projects e events — adicione mais só quando houver demanda clara.
Quais métodos HTTP devo usar, e por que isso importa?
Use os significados padrão e seja consistente:
GET= ler (sem efeitos colaterais)POST= criar ou iniciar uma açãoPATCH= atualização parcialDELETE= remover ou desabilitar
O principal ganho é previsibilidade: clientes não devem adivinhar o que um método faz.
Devo versionar minha API na URL ou em headers?
Por padrão, prefira versionamento na URL como /v1/....
É mais fácil ver em logs e screenshots, mais simples de depurar com clientes e facilita rodar v1 e v2 lado a lado quando houver uma mudança incompatível.
O que conta como uma mudança breaking em uma API?
Uma mudança é breaking se um cliente bem-comportado pode parar de funcionar sem alterar seu código. Exemplos comuns:
- Renomear campos
- Mudar tipos ou significado de campos
- Remover campos ou endpoints
- Tornar input opcional em obrigatório
- Mudar regras de autenticação ou permissões padrão
Adicionar um novo campo opcional normalmente é seguro.
Qual uma política realista de depreciação para uma API v1?
Mantenha simples:
- Anuncie cedo
- Retorne um aviso claro nas respostas (e/ou na documentação)
- Defina uma data final
Um padrão prático é uma janela de 90 dias para uma primeira API, dando tempo para os clientes migrarem sem pânico.
Devo usar paginação por página ou por cursor?
Escolha um padrão e mantenha-o em todos os endpoints de listagem.
- Use page/limit quando conjuntos forem pequenos e usuários pularem para páginas específicas.
- Use cursor quando listas crescerem muito ou mudarem com frequência.
Sempre defina uma ordenação padrão e um tie-breaker (por exemplo created_at + id) para que os resultados não saltem entre requests.
Qual limite de taxa devo definir, e o que devo retornar no 429?
Comece com um limite claro por chave (por exemplo 60 requests/minuto com pequena capacidade de burst) e ajuste conforme o tráfego real.
Ao limitar, retorne 429 e inclua:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-ResetRetry-After
Isso torna as tentativas previsíveis e reduz tickets de suporte.
Como deve ser uma boa resposta de erro de API?
Use um formato de erro único em todo lugar (incluindo 500s). Uma forma prática é:
code(identificador estável)message(legível)details(problemas por campo)request_id(para suporte)
Mantenha códigos HTTP consistentes (400/401/403/404/409/429/500) para que clientes lidem com erros de forma limpa.
Como lanço docs e um SDK pequeno sem excesso de engenharia?
Se você gera muitos endpoints rapidamente (por exemplo com Koder.ai), mantenha a superfície pública pequena e trate-a como um contrato de longo prazo.
Faça antes do lançamento:
- Escreva um exemplo realista de request + response por endpoint
- Trave formas de paginação e erros
- Adicione chaves de idempotência para
POSTimportantes - Construa um “cliente falso” internamente para identificar pontos confusos
Depois publique um SDK pequeno que ajude com autenticação, timeouts, retries para requisições seguras e paginação — sem esconder como a API HTTP funciona.