Voltar ao blog
APIversionamentoOpenAPIcontratosretrocompatibilidadeRESTbackend

Design de APIs que sobrevivem: versionamento, contratos e retrocompatibilidade

2026-07-10
Design de APIs que sobrevivem: versionamento, contratos e retrocompatibilidade

Uma API não é código, é uma promessa. No momento em que o primeiro cliente integra com o seu endpoint, você assinou um contrato: "enquanto você chamar assim, vou responder assado". O software por trás pode mudar todo dia; o contrato, não. A diferença entre uma API que dura anos e uma que gera incidentes a cada release está inteiramente na disciplina com que essa promessa é gerenciada.

Este guia é sobre a parte difícil de APIs: como fazê-las evoluir sem quebrar quem já depende delas. Versionamento, contratos, mudanças seguras e deprecação com respeito.

O contrato é o produto

Quando falamos de uma API pública ou interna consumida por vários times, o consumidor não enxerga a sua arquitetura, seu banco ou sua linguagem. Ele enxerga apenas o contrato: os endpoints, os formatos de requisição e resposta, os códigos de status, os erros. Esse contrato é a superfície pública, e tudo o que está nela é território sagrado.

A consequência prática é dura: você pode refatorar o mundo inteiro por trás, mas não pode renomear um campo de resposta sem consequências. Por isso, o primeiro princípio de um bom design de API é separar o que é interface do que é implementação, e proteger a interface com contratos explícitos.

Mudanças breaking x não-breaking

Toda mudança em uma API cai em uma de duas categorias, e saber diferenciá-las é o coração do assunto.

Uma mudança não-breaking (retrocompatível) é aquela que um cliente antigo continua funcionando sem alteração:

  • Adicionar um endpoint novo.
  • Adicionar um campo opcional na requisição.
  • Adicionar um campo novo na resposta (clientes bem-comportados ignoram o que não conhecem).
  • Adicionar um novo valor a um enum de saída, com cuidado.

Uma mudança breaking quebra clientes existentes:

  • Remover ou renomear um campo.
  • Mudar o tipo de um campo (string vira número).
  • Tornar obrigatório um campo que era opcional.
  • Mudar semântica, código de status ou o formato de erro.
  • Remover um endpoint ou apertar validações.
Tipo de mudança Exemplo Quebra clientes? Exige nova versão?
Adicionar endpoint POST /faturas Não Não
Adicionar campo opcional na resposta + status_pagamento Não Não
Tornar campo obrigatório cpf passa a ser required Sim Sim
Renomear campo nome → nome_completo Sim Sim
Mudar tipo valor: "10.00" → 10.0 Sim Sim
Remover endpoint DELETE /v1/legado Sim Sim

A regra de ouro: adicionar é barato, mudar e remover é caro. Projete pensando em adição.

Estratégias de versionamento

Quando uma mudança breaking é inevitável, você precisa versionar. Há três abordagens dominantes, com trade-offs reais.

Versão na URL

A mais comum e explícita: https://api.exemplo.com/v1/pedidos. A versão fica visível, é trivial rotear e cachear, e qualquer pessoa entende num relance.

  • Prós: simples, visível, fácil de testar no navegador, roteamento direto.
  • Contras: viola a pureza REST (a URL deveria identificar o recurso, não a versão) e tende a proliferar versões inteiras da API.

Versão no header

A versão vai em um cabeçalho, como Accept: application/vnd.exemplo.v2+json ou um API-Version: 2026-07-01. A URL permanece limpa e estável.

  • Prós: URLs estáveis, versionamento mais granular, alinhado com content negotiation.
  • Contras: menos visível, mais difícil de testar manualmente, exige clientes disciplinados.

Versão por data (rolling)

Popularizada por grandes plataformas: o cliente fixa uma data (API-Version: 2026-05-01) e o servidor aplica transformações para manter aquele comportamento congelado. Novas versões são datas, não números.

  • Prós: evolução contínua, o cliente escolhe quando migrar, sem saltos de versão inteira.
  • Contras: complexidade alta no servidor, que precisa manter camadas de compatibilidade.

Para a maioria dos produtos, versão na URL com v1, v2 é o padrão pragmático. Reserve o versionamento por data para APIs de grande escala e público amplo.

Contratos com OpenAPI

Contrato que vive só na cabeça do time não é contrato. A forma moderna de tornar a promessa executável é a especificação OpenAPI (antiga Swagger): um documento que descreve cada endpoint, parâmetro, schema de request/response e erro.

Um contrato formal habilita coisas que valem ouro:

  • Documentação sempre sincronizada, gerada da fonte da verdade.
  • Geração de SDKs e de tipos para clientes, em qualquer linguagem.
  • Validação automática de requests e responses contra o schema.
  • Mock servers para o front começar antes do back terminar.

O padrão ideal é contract-first: escreve-se o contrato antes do código, ele é revisado como parte do design, e o servidor é validado contra ele. O contrato deixa de ser documentação e vira o eixo do desenvolvimento.

# trecho de um contrato OpenAPI
paths:
  /v1/pedidos/{id}:
    get:
      summary: Busca um pedido
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: Pedido encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pedido'
        '404': { description: Pedido não encontrado }
components:
  schemas:
    Pedido:
      type: object
      required: [id, status, total]
      properties:
        id: { type: string, format: uuid }
        status: { type: string, enum: [aberto, pago, cancelado] }
        total: { type: number }

Deprecação com respeito

Toda versão morre um dia, mas matar sem aviso é como derrubar uma ponte com carros em cima. Um processo de deprecação responsável tem etapas claras:

  1. Anuncie com antecedência generosa, por changelog, e-mail e documentação.
  2. Sinalize na resposta: use o header Deprecation: true e Sunset: <data> para que clientes automatizados detectem o fim programado.
  3. Monitore o uso residual: meça quantos clientes ainda batem na versão antiga antes de desligá-la.
  4. Ofereça um caminho de migração: guia claro do que mudou e como adaptar, idealmente com exemplos lado a lado.
  5. Desligue de fato só depois de o uso cair a um nível aceitável, e comunique o desligamento efetivo.

A cortesia técnica aqui é reputação: APIs que quebram sem aviso perdem a confiança dos integradores rápido, e confiança é o ativo mais caro de reconstruir.

Testes de contrato

Como garantir que o servidor nunca viole a promessa por acidente, num deploy comum de sexta à tarde? Com testes de contrato. Diferente de testes de integração tradicionais, eles verificam especificamente que a resposta continua conforme o schema acordado.

  • Provider tests: validam que a API respeita seu próprio contrato OpenAPI a cada build.
  • Consumer-driven contracts (ex.: Pact): o consumidor descreve o que espera, e o provedor é testado contra essas expectativas, pegando quebras antes de chegarem em produção.

Rodando isso no CI, uma mudança breaking acidental falha o pipeline em vez de gerar um incidente. O contrato deixa de ser confiança e passa a ser garantia automatizada.

Boas práticas que envelhecem bem

Alguns hábitos de design tornam a evolução muito menos dolorosa:

  • Prefira campos a posições: objetos nomeados evoluem melhor que arrays posicionais.
  • Erros estruturados e consistentes: um formato de erro padrão (código, mensagem, detalhes) em toda a API.
  • Paginação, filtros e ordenação pensados desde o v1, porque adicioná-los depois costuma ser breaking.
  • Idempotência em operações de escrita, com chave de idempotência, para clientes poderem repetir com segurança.
  • Tolerância do cliente: ensine consumidores a ignorar campos desconhecidos: isso torna futuras adições não-breaking de fato.

APIs são infraestrutura de longo prazo

Projetar uma API é assumir responsabilidade por um contrato que pode durar mais que o time que o escreveu. Versionamento claro, contratos formais em OpenAPI, disciplina entre mudanças breaking e não-breaking, deprecação comunicada e testes de contrato no CI são o que separa uma plataforma confiável de uma fonte crônica de incidentes. Não é sobre nunca mudar: é sobre mudar sem trair quem confiou em você.

Na ConstructorTI, projetamos e evoluímos APIs pensando no ciclo de vida inteiro: contract-first com OpenAPI, versionamento pragmático, testes de contrato automatizados e estratégias de deprecação que preservam a confiança dos integradores. Se a sua API está difícil de evoluir, quebrando clientes a cada release ou sem contrato formal, fale com o nosso time de engenharia e saia da conversa com um plano de versionamento e retrocompatibilidade sob medida.