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.

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.
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:
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 é, 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 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.
Uma mudança é breaking se um cliente bem-comportado pode parar de funcionar sem alterar seu código. Exemplos comuns:
customer_id para customerId)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.
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.
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:
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=="
}
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 novoClientes 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:
Retry-After quando presenteExemplo: 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.
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.
Idempotency-Key em endpoints POST selecionados.Esse único header previne muitos casos dolorosos quando redes são instáveis ou clientes batem timeouts.
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{ "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.
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.
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:
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.
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.
Um bom conjunto inicial é:
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.
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.
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:
Um hábito simples ajuda: toda resposta deve incluir um request_id, e todo 429 deve explicar quando tentar de novo.
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:
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:
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).
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.
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.
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 desabilitarO principal ganho é previsibilidade: clientes não devem adivinhar o que um método faz.
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.
Uma mudança é breaking se um cliente bem-comportado pode parar de funcionar sem alterar seu código. Exemplos comuns:
Adicionar um novo campo opcional normalmente é seguro.
Mantenha simples:
Um padrão prático é uma janela de 90 dias para uma primeira API, dando tempo para os clientes migrarem sem pânico.
Escolha um padrão e mantenha-o em todos os endpoints de listagem.
Sempre defina uma ordenação padrão e um tie-breaker (por exemplo + ) para que os resultados não saltem entre requests.
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-LimitUse 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.
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:
POST importantescreated_atidX-RateLimit-RemainingX-RateLimit-ResetRetry-AfterIsso torna as tentativas previsíveis e reduz tickets de suporte.
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.