Use Cases (Atualizado: 02/06/2026)

Design prático de API com Claude Code: OpenAPI, testes e breaking changes

Projete REST APIs confiáveis com Claude Code: OpenAPI, mock, testes, versionamento, segurança e armadilhas.

Design prático de API com Claude Code: OpenAPI, testes e breaking changes

Design de API não é escolher URLs bonitas. É definir um contrato: o que o cliente pode enviar, o que o servidor retorna e como uma falha será explicada.

Quando esse contrato fica vago, frontend, app mobile, integrações de parceiros, testes e monitoramento começam a interpretar a API de formas diferentes. Corrigir depois que clientes já dependem dela é muito mais caro.

Claude Code ajuda bastante, mas funciona melhor como revisor de design, não apenas como gerador de código. Peça para criar um rascunho OpenAPI, revisar endpoints, gerar mock e testes, e detectar mudanças incompatíveis antes do deploy.

Use referências oficiais: OpenAPI Specification, RFC 9110 HTTP Semantics, JSON Schema docs e OWASP API Security Top 10. Para avançar na implementação, leia também desenvolvimento de APIs de produção, automação de testes de API e versionamento de API.

O que o design de API decide

Uma API é uma interface para outro programa. Uma tela humana se explica com botões, texto e layout; uma API se explica com caminhos, métodos HTTP, status codes, campos JSON, schemas, exemplos e respostas de erro.

Para começar, decida cinco pontos.

DecisãoExplicação simplesExemplo
RecursoO substantivo exposto pela APIorders, customers, invoices
OperaçãoO que acontece com o recursoGET, POST, PATCH, DELETE
SchemaForma e regras do JSONitems precisa ter pelo menos um item
ErroComo a falha aparece400, 401, 403, 404, 422 com detalhes
CompatibilidadeComo não quebrar clientesCampo obrigatório novo é breaking

REST não precisa começar de forma teórica. Na prática, use URLs com substantivos e deixe a ação para o método HTTP. POST /orders é mais claro que POST /orders/create, e GET /orders/ord_123 é melhor que GET /getOrder?id=ord_123.

Workflow com Claude Code

Não peça para Claude Code projetar, implementar, testar e documentar tudo em uma única instrução. Separe em passos revisáveis.

flowchart TD
  A["Resumir regras de negócio"] --> B["Criar contrato OpenAPI"]
  B --> C["Revisar HTTP, schema e segurança"]
  C --> D["Gerar mock e testes de API"]
  D --> E["Verificar breaking changes no CI"]
  E --> F["Implementar, documentar e publicar"]

OpenAPI é um contrato legível por máquina para APIs HTTP. JSON Schema descreve a forma e as restrições do JSON. Status codes HTTP dão significado comum para sucesso e falha. Claude Code conecta essas peças, mas a fonte de verdade continua sendo a especificação oficial e seus testes.

Em um pequeno projeto de verificação, Masa percebeu que pedir apenas uma lista de endpoints gerava algo aceitável. O problema apareceu quando autenticação, paginação, idempotency key e detalhes de erro foram adicionados depois. Um bom prompt inicial já pergunta como o cliente se recupera de falhas.

Exemplo para copiar e executar

O exemplo abaixo não usa pacotes externos. Ele mostra OpenAPI, um servidor mock e uma verificação de breaking change.

mkdir api-design-lab
cd api-design-lab
mkdir docs examples
node --version

Crie docs/openapi.yaml. A página oficial de OpenAPI mostra a versão publicada mais recente; aqui usamos 3.1 por compatibilidade prática com ferramentas.

openapi: 3.1.0
info:
  title: Orders API
  version: 1.0.0
servers:
  - url: https://api.example.com
paths:
  /v1/orders:
    post:
      summary: Create an order
      operationId: createOrder
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrderRequest"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Problem"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    CreateOrderRequest:
      type: object
      required: [customerId, items]
      properties:
        customerId:
          type: string
          minLength: 3
        items:
          type: array
          minItems: 1
          items:
            type: object
            required: [sku, quantity]
            properties:
              sku:
                type: string
                minLength: 3
              quantity:
                type: integer
                minimum: 1
    Order:
      type: object
      required: [id, status, customerId, total]
      properties:
        id:
          type: string
        status:
          type: string
          enum: [accepted, cancelled]
        customerId:
          type: string
        total:
          type: integer
    Problem:
      type: object
      required: [type, title, status, detail]
      properties:
        type:
          type: string
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        errors:
          type: array
          items:
            type: object

Crie examples/mock-server.mjs.

import { createServer } from "node:http";
import { randomUUID } from "node:crypto";

function readJson(req) {
  return new Promise((resolve, reject) => {
    let body = "";
    req.on("data", (chunk) => {
      body += chunk;
      if (body.length > 1_000_000) req.destroy(new Error("Body too large"));
    });
    req.on("end", () => {
      if (!body) return resolve({});
      try {
        resolve(JSON.parse(body));
      } catch (error) {
        reject(error);
      }
    });
    req.on("error", reject);
  });
}

function send(res, status, body, headers = {}) {
  res.writeHead(status, {
    "content-type": "application/json; charset=utf-8",
    "x-content-type-options": "nosniff",
    ...headers,
  });
  res.end(JSON.stringify(body, null, 2));
}

function problem(status, title, detail, errors = []) {
  return {
    type: "https://example.com/problems/request",
    title,
    status,
    detail,
    errors,
  };
}

function validateOrder(input) {
  const errors = [];
  if (typeof input.customerId !== "string" || input.customerId.length < 3) {
    errors.push({
      path: "customerId",
      message: "customerId must be a string with 3+ characters",
    });
  }
  if (!Array.isArray(input.items) || input.items.length === 0) {
    errors.push({ path: "items", message: "items must contain at least one item" });
  }
  for (const [index, item] of (input.items ?? []).entries()) {
    if (typeof item.sku !== "string" || item.sku.length < 3) {
      errors.push({
        path: `items.${index}.sku`,
        message: "sku must be a string with 3+ characters",
      });
    }
    if (!Number.isInteger(item.quantity) || item.quantity < 1) {
      errors.push({
        path: `items.${index}.quantity`,
        message: "quantity must be a positive integer",
      });
    }
  }
  return errors;
}

const server = createServer(async (req, res) => {
  const url = new URL(req.url ?? "/", "http://localhost");

  if (req.method === "GET" && url.pathname === "/health") {
    return send(res, 200, { ok: true });
  }

  const customerMatch = url.pathname.match(/^\/v1\/customers\/([a-z0-9-]+)$/);
  if (req.method === "GET" && customerMatch) {
    return send(res, 200, {
      id: customerMatch[1],
      name: "Aki Tanaka",
      plan: "pro",
    });
  }

  if (req.method === "POST" && url.pathname === "/v1/orders") {
    const idempotencyKey = req.headers["idempotency-key"];
    if (!idempotencyKey) {
      return send(
        res,
        400,
        problem(400, "Missing Idempotency-Key", "POST /v1/orders requires the header.")
      );
    }

    try {
      const body = await readJson(req);
      const errors = validateOrder(body);
      if (errors.length > 0) {
        return send(res, 422, problem(422, "Invalid request body", "Fix errors.", errors));
      }
      return send(
        res,
        201,
        {
          id: `ord_${randomUUID()}`,
          status: "accepted",
          customerId: body.customerId,
          total: 4200,
        },
        { location: "/v1/orders/example" }
      );
    } catch {
      return send(res, 400, problem(400, "Malformed JSON", "Request body must be JSON."));
    }
  }

  return send(res, 404, problem(404, "Not found", `${req.method} ${url.pathname} is undefined.`));
});

server.listen(3000, () => {
  console.log("Mock API running at http://localhost:3000");
});

Execute o servidor e chame a API em outro terminal.

node examples/mock-server.mjs
curl -i http://localhost:3000/health
curl -i -X POST http://localhost:3000/v1/orders \
  -H "content-type: application/json" \
  -H "idempotency-key: demo-001" \
  -d '{"customerId":"cus_123","items":[{"sku":"book-1","quantity":2}]}'
curl -i -X POST http://localhost:3000/v1/orders \
  -H "content-type: application/json" \
  -H "idempotency-key: demo-002" \
  -d '{"customerId":"x","items":[]}'

Crie examples/contract-check.mjs. Ele falha de propósito para mostrar mudanças incompatíveis.

import assert from "node:assert/strict";

const previous = {
  paths: {
    "/v1/orders": {
      post: {
        request: {
          required: ["customerId", "items"],
          properties: ["customerId", "items", "couponCode"],
        },
        response: {
          required: ["id", "status", "customerId", "total"],
          properties: ["id", "status", "customerId", "total"],
        },
      },
    },
  },
};

const next = structuredClone(previous);
next.paths["/v1/orders"].post.request.required.push("shippingAddress");
next.paths["/v1/orders"].post.response.properties =
  next.paths["/v1/orders"].post.response.properties.filter((name) => name !== "total");

function diffContract(oldSpec, newSpec) {
  const breaking = [];
  for (const [path, methods] of Object.entries(oldSpec.paths)) {
    for (const [method, oldOperation] of Object.entries(methods)) {
      const newOperation = newSpec.paths[path]?.[method];
      if (!newOperation) {
        breaking.push(`${method.toUpperCase()} ${path} was removed`);
        continue;
      }

      const oldRequired = new Set(oldOperation.request.required);
      for (const field of newOperation.request.required) {
        if (!oldRequired.has(field)) {
          breaking.push(`${method.toUpperCase()} ${path} now requires "${field}"`);
        }
      }

      const newResponseFields = new Set(newOperation.response.properties);
      for (const field of oldOperation.response.properties) {
        if (!newResponseFields.has(field)) {
          breaking.push(`${method.toUpperCase()} ${path} removed response "${field}"`);
        }
      }
    }
  }
  return breaking;
}

const breaking = diffContract(previous, next);
console.log(breaking.join("\n") || "No breaking changes found");
assert.equal(breaking.length, 0, "Breaking API changes detected");
node examples/contract-check.mjs

Nesse caso, falhar é o resultado esperado. O script encontra um novo campo obrigatório e um campo de resposta removido.

Prompts para Claude Code

Separe rascunho, revisão, geração de exemplos e compatibilidade.

claude -p "
Crie um rascunho OpenAPI em docs/openapi.yaml para uma API de pedidos e-commerce.
Recursos: customers, orders, invoices.
Inclua summary, operationId, requestBody, responses, examples e bearerAuth.
Use OpenAPI 3.1 e restrições no estilo JSON Schema.
"
claude -p "
Revise docs/openapi.yaml como revisor de design de API.
Retorne Findings por severidade primeiro e ainda não edite arquivos.
Verifique semântica method/status da RFC 9110, schemas vagos, pagination,
idempotency, authentication e riscos comuns de OWASP API Security.
"
claude -p "
Gere um Mock server Node.js e exemplos de API tests a partir de docs/openapi.yaml.
Cubra sucesso, falha de autenticação, falha de validação e recurso inexistente.
Mantenha linhas longas abaixo de 150 caracteres e adicione comandos no README.
"
claude -p "
Compare o docs/openapi.yaml atual com a versão em HEAD.
Liste breaking changes antes de sugerir edições.
Verifique paths removidos, novos campos obrigatórios, campos de resposta removidos,
mudanças de status code e mudanças de auth scope.
"

Casos de uso reais

O primeiro caso é uma API de pedidos SaaS. Admin, billing, e-mails e exportações contábeis leem o mesmo Order. Se total, moeda, impostos e cancelamento forem vagos, cada integração cria sua própria regra.

O segundo caso é uma API de perfil mobile. Versões antigas do app podem ficar ativas por meses. Remover um campo ou mudar o significado de um enum quebra clientes que não serão atualizados imediatamente.

O terceiro caso é uma API B2B para parceiros. Desenvolvedores externos não conhecem suas convenções internas. Eles precisam de códigos de erro estáveis, rate limits, regras de retry, sandbox e exemplos.

O quarto caso é uma API interna de administração. Interna não significa segura. Autorização por objeto continua essencial: saber o ID de um pedido não deve permitir ler dados de outro tenant.

Falhas e armadilhas

Uma falha comum é colocar verbos no path: /cancelOrder, /getUserOrders, /updateOrderStatus. Com o tempo, os nomes ficam inconsistentes. Modele recursos primeiro e use subrecursos quando necessário.

Outra falha é retornar 200 para todo erro de negócio. Isso complica monitoramento, SDKs, retries e lógica do cliente. Use 400 para input malformado, 401 sem autenticação, 403 proibido, 404 ausente e 422 para input semanticamente inválido.

Esquecer segurança de retry em POST também custa caro. Criação de pedido ou início de pagamento pode ser reenviado após timeout. Planeje Idempotency-Key desde o início.

Schema ambíguo gera bugs lentos. Decida se null significa limpar, desconhecido ou não enviado. Defina campos obrigatórios, mínimos, limites de paginação, timezone e propriedades adicionais.

Versionamento, erros, schema e segurança

Versionamento é mais do que adicionar /v1. O time precisa definir o que é breaking. Campo opcional novo geralmente é seguro; campo obrigatório novo, remoção de resposta, mudança de status ou permissão mais rígida pode quebrar clientes.

Erro bom diz o que o cliente deve fazer depois. Invalid request não basta. Use uma forma estável com type, title, status, detail e errors por campo quando útil. Não exponha stack trace, SQL ou IDs internos em produção.

Schema deve codificar restrições, não só exemplos. Formatos de ID, comprimento mínimo, limites de array, paginação e datas reduzem suposições no cliente.

Segurança separa autenticação de autorização. Bearer token não resolve se GET /orders/{id} devolve pedido de outro tenant. Evite API keys em query, reduza dados sensíveis, aplique rate limit e mantenha audit logs.

Monetização e consultoria

Quem pesquisa design de API normalmente tem um projeto real: integração pública, backend mobile, regras de revisão do time ou API para parceiros. O artigo deve mostrar um fluxo aplicável, não apenas conceitos.

ClaudeCodeLab pode ajudar com revisão de design de API usando Claude Code, organização de OpenAPI, automação de testes e checks de breaking change. Times podem começar por treinamento e consultoria; desenvolvedores individuais podem usar os recursos gratuitos.

Resultado verificado

Testei o código com Node v24.14.1. GET /health retornou 200, um POST /v1/orders válido retornou 201 e items vazio retornou 422. contract-check.mjs falhou intencionalmente e mostrou o novo campo obrigatório e o campo de resposta removido. O fluxo leva de prompts do Claude Code a OpenAPI, mock, erros e verificação útil para CI.

#claude-code #api #rest #openapi #design
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.