Advanced (Atualizado: 02/06/2026)

Versionamento de API com Claude Code: guia prático para contratos seguros

Planeje versionamento de API com Claude Code: OpenAPI, testes de compatibilidade, headers de depreciação e rollout.

Versionamento de API com Claude Code: guia prático para contratos seguros

Versionar uma API não é apenas colocar/v2 em uma rota. É uma promessa de compatibilidade para cada aplicativo móvel, integração de parceiro, serviço interno, consumidor de Webhook e job batch que já depende dessa API. Um campo renomeado pode deixar a resposta nova mais bonita, mas quebrar um cliente antigo.

Claude Code ajuda porque consegue ler o repositório, editar arquivos e executar comandos, como descreve a documentação oficial do Claude Code. O risco aparece quando o pedido é vago. “Modernize esta API” pode levar o assistente a otimizar a forma nova e esquecer consumidores antigos. Para evitar isso, entregue contrato, regras de compatibilidade, plano de rollout e comandos de verificação antes da implementação.

Este guia cobre URL, header e media type como estratégias de versionamento, contratos OpenAPI, compatibilidade retroativa, headersDeprecation eSunset, política de changelog, consumer tests, rollout com fallback e prompts que impedem breaking changes no Claude Code. As referências oficiais são OpenAPI Specification, RFC 9745 para Deprecation e RFC 8594 para Sunset.

Para aprofundar a implementação, veja também desenvolvimento de API com Claude Code, code review com Claude Code e versionamento com Changesets.

Comece Pelo Contrato De Compatibilidade

O objetivo do versionamento não é manter código antigo para sempre. É dar aos consumidores uma janela previsível de migração. Masa testou isso em uma pequena API de pedidos: quando o prompt dizia apenas “adicione v2 e renomeie os campos do cliente”, o código gerado passava no dashboard novo, mas quebrava uma exportação CSV antiga. Faltavam regras específicas: manter o formato da resposta v1, publicar a data de depreciação, adicionar consumer test e escrever o guia de migração.

Três casos de uso são comuns:

Caso de usoRestrição principalEstilo geralmente adequado
API REST pública para apps móveisVersões antigas do app permanecem por mesesVersionamento na URL
API B2B para parceiros SaaSClientes migram no próprio calendárioURL ou header explícito
Microsserviços internosClientes podem ser atualizados juntosHeader ou media type

Antes de pedir código ao Claude Code, liste consumidores atuais, janela mínima de suporte, definição de breaking change e métricas de acompanhamento. Breaking change não é só rota removida. Também pode ser campo de resposta renomeado, novo campo obrigatório, envelope de erro diferente, ordenação padrão alterada ou paginação incompatível.

Escolha URL, Header Ou Media Type

Onde a versão fica afeta roteamento, cache, documentação, geração de SDK e suporte. Para a maioria das APIs públicas, versionamento na URL é o começo mais pragmático: aparece nos logs, é simples no API Gateway e fácil de testar comcurl. A desvantagem é que a URI do recurso passa a conter uma versão do produto. /api/v1/orders/123 e/api/v2/orders/123 parecem recursos diferentes.

EstiloExemploForçaFalha comum
URL/api/v1/ordersRotas, docs e debug clarosRotas antigas ficam vivas e o roteador duplica
Header customizadoAPI-Version: 2URL estável, bom para clientes controladosHeader é esquecido; cache precisa deVary: API-Version
Media typeAccept: application/vnd.acme.orders.v2+jsonUsa negociação de conteúdo HTTPOpenAPI, SDK e suporte ficam mais complexos

Com media type, envieVary: Accept para que caches intermediários não misturem respostas v1 e v2. Com header customizado, envieVary: API-Version. Mesmo usando URL, trate v1 e v2 como contratos separados em OpenAPI quando a compatibilidade da resposta mudar.

Use OpenAPI Como Fonte Da Verdade

OpenAPI descreve APIs HTTP em formato legível por máquina: caminhos, métodos, parâmetros, bodies, respostas e segurança. Em termos simples, é a promessa da API antes da implementação. O campoopenapi representa a versão da especificação OpenAPI;info.version representa a versão do documento da sua API. Deixe essa diferença explícita para Claude Code.

O exemplo abaixo mantém v1 documentada e marcada como deprecated enquanto adiciona v2. Ele usaopenapi: 3.1.0 por boa compatibilidade com validadores e geradores; verifique a documentação oficial do OpenAPI antes de adotar uma versão de especificação mais recente no time.

openapi: 3.1.0
info:
  title: Acme Orders API
  version: 2.0.0
servers:
  - url: https://api.example.com
paths:
  /api/v1/orders/{orderId}:
    get:
      operationId: getOrderV1
      summary: Get an order in the legacy v1 shape
      deprecated: true
      x-deprecated-at: "2026-03-31T00:00:00Z"
      x-sunset-at: "2026-12-31T23:59:59Z"
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Legacy order response
          headers:
            Deprecation:
              schema:
                type: string
              description: RFC 9745 structured date, for example @1774915200
            Sunset:
              schema:
                type: string
              description: RFC 8594 HTTP-date when v1 may stop responding
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderV1Envelope"
  /api/v2/orders/{orderId}:
    get:
      operationId: getOrderV2
      summary: Get an order in the current v2 shape
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Current order response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderV2Envelope"
components:
  schemas:
    OrderV1Envelope:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [id, customerName, totalCents, currency]
          properties:
            id:
              type: string
            customerName:
              type: string
            totalCents:
              type: integer
            currency:
              type: string
    OrderV2Envelope:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [id, customer, amount, status]
          properties:
            id:
              type: string
            customer:
              type: object
              required: [displayName]
              properties:
                displayName:
                  type: string
            amount:
              type: object
              required: [value, currency]
              properties:
                value:
                  type: integer
                currency:
                  type: string
            status:
              type: string
              enum: [paid, shipped]

Entregue esse YAML ao Claude Code antes do código. A instrução precisa dizer: não remova campos v1, não altere status codes v1 e atualize testes e CHANGELOG sempre que o contrato mudar.

Implemente Compatibilidade Retroativa Em Node

O servidor TypeScript abaixo usa apenas APIs nativas do Node. Salve comoapi-versioning-demo.ts para testar versionamento por URL, headerAPI-Version e media typeAccept, sem banco de dados nem framework. v1 mantém o formato legado, v2 retorna o formato atual e v1 inclui headers de depreciação.

import { createServer } from "node:http";
import { parse } from "node:url";

type ApiVersion = "v1" | "v2";

type OrderRow = {
  id: string;
  customerName: string;
  totalCents: number;
  currency: "JPY" | "USD";
  status: "paid" | "shipped";
  createdAt: string;
};

const orders = new Map<string, OrderRow>([
  [
    "o_100",
    {
      id: "o_100",
      customerName: "Masa Tanaka",
      totalCents: 129800,
      currency: "JPY",
      status: "paid",
      createdAt: "2026-06-02T09:00:00.000Z",
    },
  ],
]);

function detectVersion(req: { headers: Record<string, string | string[] | undefined> }, pathname: string) {
  const pathVersion = pathname.match(/^\/api\/(v[12])\//)?.[1] as ApiVersion | undefined;
  if (pathVersion) return { version: pathVersion, source: "path" };

  const header = req.headers["api-version"];
  if (typeof header === "string") {
    const normalized = header.startsWith("v") ? header : `v${header}`;
    if (normalized === "v1" || normalized === "v2") {
      return { version: normalized, source: "header" };
    }
    throw new Error(`Unsupported API-Version: ${header}`);
  }

  const accept = req.headers.accept;
  if (typeof accept === "string") {
    const mediaMatch = accept.match(/application\/vnd\.acme\.orders\.v([12])\+json/);
    if (mediaMatch) {
      return { version: `v${mediaMatch[1]}` as ApiVersion, source: "media-type" };
    }
  }

  return { version: "v1" as ApiVersion, source: "default" };
}

function orderIdFrom(pathname: string) {
  return pathname.match(/^\/api\/(?:v[12]\/)?orders\/([^/]+)$/)?.[1];
}

function toV1(row: OrderRow) {
  return {
    data: {
      id: row.id,
      customerName: row.customerName,
      totalCents: row.totalCents,
      currency: row.currency,
    },
  };
}

function toV2(row: OrderRow) {
  return {
    data: {
      id: row.id,
      customer: { displayName: row.customerName },
      amount: { value: row.totalCents, currency: row.currency },
      status: row.status,
      createdAt: row.createdAt,
    },
  };
}

function addDeprecationHeaders(res: import("node:http").ServerResponse) {
  const deprecatedAt = Math.floor(Date.parse("2026-03-31T00:00:00Z") / 1000);
  res.setHeader("Deprecation", `@${deprecatedAt}`);
  res.setHeader("Sunset", new Date("2026-12-31T23:59:59Z").toUTCString());
  res.setHeader(
    "Link",
    [
      '<https://docs.example.com/api/deprecations/v1-to-v2>; rel="deprecation"; type="text/html"',
      '<https://docs.example.com/api/sunset-policy>; rel="sunset"; type="text/html"',
    ].join(", "),
  );
}

function sendJson(res: import("node:http").ServerResponse, status: number, body: unknown) {
  res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
  res.end(JSON.stringify(body, null, 2));
}

const server = createServer((req, res) => {
  const pathname = parse(req.url ?? "/").pathname ?? "/";
  const orderId = orderIdFrom(pathname);

  if (!orderId) {
    return sendJson(res, 404, { error: "not_found", message: "Route not found" });
  }

  let detected: ReturnType<typeof detectVersion>;
  try {
    detected = detectVersion(req, pathname);
  } catch (error) {
    return sendJson(res, 400, {
      error: "unsupported_version",
      message: error instanceof Error ? error.message : "Unsupported API version",
      supportedVersions: ["v1", "v2"],
    });
  }

  const row = orders.get(orderId);
  if (!row) {
    return sendJson(res, 404, { error: "order_not_found", orderId });
  }

  res.setHeader("Vary", "Accept, API-Version");
  res.setHeader("X-API-Version", detected.version);
  res.setHeader("X-API-Version-Source", detected.source);

  if (detected.version === "v1") {
    addDeprecationHeaders(res);
    return sendJson(res, 200, toV1(row));
  }

  return sendJson(res, 200, toV2(row));
});

const port = Number(process.env.PORT ?? 18080);

server.listen(port, () => {
  console.log(`API versioning demo: http://localhost:${port}`);
});
npm init -y
npm install -D tsx typescript @types/node
PORT=18080 npx tsx api-versioning-demo.ts &
SERVER_PID=$!
sleep 1

curl -i http://localhost:18080/api/v1/orders/o_100
curl -i -H "API-Version: 2" http://localhost:18080/api/orders/o_100
curl -i -H "Accept: application/vnd.acme.orders.v2+json" http://localhost:18080/api/orders/o_100

kill "$SERVER_PID"

A escolha importante é a camada de transformação. v1 não deve reutilizar a resposta v2 e torcer para os clientes antigos aceitarem. Cada versão mapeia o modelo interno para a forma pública prometida.

Publique Headers E Política De Versão

Exemplos antigos costumam mostrarDeprecation: true. Pela RFC 9745,Deprecation é um valor Date estruturado, como@1774915200. Sunset, definido pela RFC 8594, é uma data HTTP que indica quando o recurso pode deixar de responder. Esses headers são sinais em runtime, não substituem o plano de migração.

Coloque a política no repositório:

currentApiVersion: v2
minimumSupportWindowMonths: 12
breakingChangeRequires:
  - new-major-version
  - migration-guide
  - consumer-test
  - owner-approval
deprecatedVersions:
  - version: v1
    deprecatedAt: "2026-03-31T00:00:00Z"
    sunsetAt: "2026-12-31T23:59:59Z"
    replacement: "/api/v2/orders/{orderId}"
    migrationGuide: "https://docs.example.com/api/deprecations/v1-to-v2"

O CHANGELOG deve separar adicionado, alterado, deprecated e remoção prevista. Uma boa entrada informa quem é afetado, o que precisa mudar, qual endpoint substitui o antigo e quando a versão antiga pode parar de responder.

Adicione Consumer Tests Antes Do Refactor

Consumer test expressa o que o cliente ainda espera. Ele é útil quando Claude Code tenta limpar uma camada que parece duplicada. O teste abaixo confirma que v1 ainda temcustomerName e não retorna por acidente o objetocustomer de v2.

import assert from "node:assert/strict";
import test from "node:test";

const baseUrl = process.env.API_BASE_URL ?? "http://localhost:18080";

test("v1 keeps the legacy response shape", async () => {
  const res = await fetch(`${baseUrl}/api/v1/orders/o_100`);
  assert.equal(res.status, 200);
  assert.match(res.headers.get("deprecation") ?? "", /^@\d+$/);
  assert.match(res.headers.get("sunset") ?? "", /GMT$/);

  const body = await res.json();
  assert.equal(body.data.customerName, "Masa Tanaka");
  assert.equal(body.data.customer, undefined);
});

test("v2 returns the current response shape", async () => {
  const res = await fetch(`${baseUrl}/api/orders/o_100`, {
    headers: { "API-Version": "2" },
  });
  assert.equal(res.status, 200);

  const body = await res.json();
  assert.equal(body.data.customer.displayName, "Masa Tanaka");
  assert.equal(body.data.amount.currency, "JPY");
  assert.equal(body.data.customerName, undefined);
});
PORT=18080 npx tsx api-versioning-demo.ts &
SERVER_PID=$!
sleep 1
API_BASE_URL=http://localhost:18080 node --test version-contract.test.mjs
kill "$SERVER_PID"

Se o projeto usa lint de OpenAPI, inclua no mesmo fluxo:

npx @redocly/cli lint openapi.yaml

Com esses comandos no prompt, Claude Code tem uma meta objetiva. “Mantenha compatível” sozinho ainda deixa muita interpretação.

Faça Rollout Em Fases Com Fallback

Incidentes de versionamento são previsíveis. O time muda schema de banco e resposta no mesmo deploy, dificultando rollback. A data de sunset é anunciada antes de medir tráfego real de v1. O SDK é atualizado, mas usuários que chamam HTTP diretamente são esquecidos. Ou a documentação diz deprecated, enquanto métricas e alertas não identificam consumidores restantes.

Separe o rollout: adicionar v2, adicionar headers de depreciação em v1, medir uso por versão, publicar guia de migração, atualizar SDKs, avisar parceiros, aplicar sunset e só depois remover v1. O fallback precisa provar que v1 continua funcionando se v2 for desativada, que clientes antigos ignoram campos novos e que migrações de dados mantêm pelo menos leitura compatível.

mkdir -p tmp/version-snapshots
BASE_URL=${BASE_URL:-http://localhost:18080}

for order_id in o_100 missing; do
  curl -sS -D "tmp/version-snapshots/${order_id}.v1.headers" \
    "$BASE_URL/api/v1/orders/$order_id" \
    > "tmp/version-snapshots/${order_id}.v1.json" || true

  curl -sS -D "tmp/version-snapshots/${order_id}.v2.headers" \
    -H "API-Version: 2" \
    "$BASE_URL/api/orders/$order_id" \
    > "tmp/version-snapshots/${order_id}.v2.json" || true
done

Anexe esses snapshots ao pull request ou cole no Claude Code para pedir um resumo de compatibilidade. Eles não substituem testes, mas tornam diferenças de comportamento visíveis.

Prompts Para Evitar Breaking Changes

Claude Code responde melhor quando o prompt inclui contrato, mudanças proibidas e verificações obrigatórias.

Adicione v2 à API existente. Trate os arquivos OpenAPI como fonte da verdade. Não altere o formato de resposta, status codes nem headers de depreciação da v1.

Antes de editar, liste:
- possíveis breaking changes
- campos que devem permanecer na v1
- campos adicionados ou alterados na v2
- consumer tests que você vai adicionar

Depois de editar, execute:
- npm test
- npx @redocly/cli lint openapi.yaml
- comparações curl para v1 e v2

Na resposta final, inclua risco de compatibilidade, notas para o guia de migração e passos de rollback.

Antes do merge, use um prompt de revisão:

Revise este diff como revisão de compatibilidade de API.
Verifique:
- campos obrigatórios de resposta v1 não foram removidos, renomeados nem tiveram tipo alterado
- erros, HTTP statuses, paginação e ordenação não mudaram inesperadamente
- Deprecation, Sunset, Link e Vary seguem a política
- OpenAPI, implementação, testes e CHANGELOG estão alinhados
- rollback não quebra consumidores v1

Retorne achados com nomes de arquivos e correções concretas.

Esses prompts mudam o objetivo de Claude Code de “limpar o código” para “proteger o contrato público”. Em APIs, essa diferença é essencial.

Conclusão

Versionamento seguro começa com contrato. Escolha URL, header ou media type conforme consumidores e infraestrutura. Documente v1 e v2 em OpenAPI, mantenha transformações explícitas, publiqueDeprecation eSunset, escreva um CHANGELOG acionável e rode consumer tests antes de refatorar.

Se o seu time quer usar Claude Code no desenvolvimento de APIs, Claude Code consultation and training pode ajudar a transformar contratos, gates de CI, prompts de revisão e checklist de rollout em um processo repetível. Para começar menor, use a cheatsheet gratuita e adapte os prompts deste artigo.

Verifiquei o padrão com o servidor Node acima: v1 e v2 podem compartilhar a mesma linha interna e manter formatos públicos diferentes, e o consumer test detecta renomeação de campo imediatamente. Os detalhes mais fáceis de esquecer foram o formato Date da RFC 9745 paraDeprecation, o headerVary em versionamento por header ou media type e a revisão conjunta de OpenAPI, código, testes e CHANGELOG.

#Claude Code #design de API #versionamento de API #OpenAPI #TypeScript
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.