Use Cases (Actualizado: 2/6/2026)

Diseño práctico de API con Claude Code: OpenAPI, pruebas y cambios incompatibles

Diseña REST APIs fiables con Claude Code: OpenAPI, mock, pruebas, versionado, seguridad y errores comunes.

Diseño práctico de API con Claude Code: OpenAPI, pruebas y cambios incompatibles

Diseñar una API no consiste en inventar URLs bonitas. Consiste en definir un contrato: qué puede enviar el cliente, qué devuelve el servidor y cómo se explica un fallo.

Cuando ese contrato es ambiguo, frontend, apps móviles, integraciones externas, pruebas y observabilidad terminan interpretando cosas distintas. Repararlo al final cuesta mucho más que escribir un contrato pequeño y claro desde el principio.

Claude Code ayuda bastante, pero no conviene usarlo solo como generador de código. Úsalo como revisor de diseño: que redacte un OpenAPI, revise endpoints, genere mock y pruebas, y detecte cambios incompatibles antes de que lleguen a producción.

Trabaja con referencias oficiales: OpenAPI Specification, RFC 9110 HTTP Semantics, JSON Schema docs y OWASP API Security Top 10. Para implementar después, lee también desarrollo de APIs de producción, automatización de pruebas de API y versionado de API.

Qué significa diseñar una API

Una API es una interfaz para otro programa. Una pantalla para humanos puede explicar su intención con botones y textos; una API se explica con rutas, métodos HTTP, códigos de estado, nombres de campos, schemas, ejemplos y errores.

Para empezar, decide cinco cosas.

DecisiónExplicación simpleEjemplo
RecursoEl sustantivo que expone la APIorders, customers, invoices
OperaciónQué se hace con el recursoGET, POST, PATCH, DELETE
SchemaForma y reglas del JSONitems debe tener al menos un elemento
ErrorCómo se reporta un fallo400, 401, 403, 404, 422 con detalles
CompatibilidadCómo no romper clientesAñadir un campo obligatorio rompe

REST puede sonar abstracto, pero la regla práctica es simple: usa URLs con sustantivos y deja la acción al método HTTP. POST /orders suele ser mejor que POST /orders/create, y GET /orders/ord_123 es más claro que GET /getOrder?id=ord_123.

Flujo de trabajo con Claude Code

No pidas diseño, implementación, pruebas y documentación en una sola instrucción gigante. Divide el trabajo en pasos que puedas revisar.

flowchart TD
  A["Resumir reglas de negocio"] --> B["Redactar contrato OpenAPI"]
  B --> C["Revisar HTTP, schema y seguridad"]
  C --> D["Generar mock y pruebas API"]
  D --> E["Detectar cambios incompatibles en CI"]
  E --> F["Implementar, documentar y publicar"]

OpenAPI es un contrato legible por máquinas para APIs HTTP. JSON Schema describe la forma y las restricciones del JSON. Los códigos HTTP dan una semántica común de éxito y fallo. Claude Code puede conectar estas piezas, pero la fuente de verdad sigue siendo la especificación oficial y tus pruebas.

En una prueba pequeña, Masa comprobó que pedir primero solo una lista de endpoints daba una base razonable. El problema apareció al añadir autenticación, paginación, idempotency key y detalle de errores después. Un prompt inicial que pregunta por recuperación del cliente produce un diseño más sólido.

Ejemplo para copiar y ejecutar

Este ejemplo no usa paquetes externos. Permite probar OpenAPI, un servidor mock y una comprobación de cambios incompatibles.

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

Crea docs/openapi.yaml. La página oficial de OpenAPI muestra la versión publicada más reciente; aquí usamos 3.1 por compatibilidad práctica con herramientas.

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

Crea 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");
});

Ejecuta el servidor y llama la API desde otra 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":[]}'

Crea examples/contract-check.mjs. Falla a propósito para mostrar cambios incompatibles.

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

En este caso, fallar es correcto. El script detecta un nuevo campo obligatorio y un campo de respuesta eliminado.

Prompts para Claude Code

Separa redacción, revisión, generación de ejemplos y comprobación de compatibilidad.

claude -p "
Crea un borrador OpenAPI en docs/openapi.yaml para una API de pedidos e-commerce.
Recursos: customers, orders, invoices.
Incluye summary, operationId, requestBody, responses, examples y bearerAuth.
Usa OpenAPI 3.1 y restricciones de estilo JSON Schema.
"
claude -p "
Revisa docs/openapi.yaml como revisor de diseño de API.
Devuelve primero Findings por severidad y no edites archivos todavía.
Comprueba semántica de method/status según RFC 9110, schemas ambiguos,
pagination, idempotency, autenticación y riesgos comunes de OWASP API Security.
"
claude -p "
Genera un Mock server Node.js y ejemplos de API tests desde docs/openapi.yaml.
Cubre éxito, fallo de autenticación, fallo de validación y recurso inexistente.
Mantén líneas largas por debajo de 150 caracteres y añade comandos al README.
"
claude -p "
Compara el docs/openapi.yaml actual con la versión de HEAD.
Lista primero cambios incompatibles antes de sugerir ediciones.
Revisa paths eliminados, nuevos campos obligatorios, campos de respuesta eliminados,
cambios de status code y cambios de auth scope.
"

Casos de uso reales

El primer caso es una API de pedidos para SaaS. Panel de administración, facturación, emails y exportaciones contables leen el mismo Order. Si total, moneda, impuestos y cancelaciones no están claros, cada integración inventará su regla.

El segundo caso es una API de perfil para móvil. Las versiones antiguas de una app pueden seguir activas durante meses. Borrar un campo o cambiar el significado de un enum puede romper clientes que no se actualizan rápido.

El tercer caso es una API B2B para partners. Los desarrolladores externos no conocen tus convenciones internas. Necesitan códigos de error estables, rate limits, reglas de retry, sandbox y ejemplos.

El cuarto caso es una API interna de administración. Interna no significa segura por defecto. La autorización por objeto importa: un usuario no debería leer pedidos de otro tenant solo por conocer el ID.

Fallos y trampas

Un fallo común es meter verbos en la ruta: /cancelOrder, /getUserOrders, /updateOrderStatus. Con el tiempo aparecen nombres inconsistentes. Modela recursos primero y usa subrecursos cuando haga falta.

Otro fallo es devolver 200 para todos los errores de negocio. Eso complica monitoreo, SDKs, retries y manejo de errores. Usa 400 para input mal formado, 401 para falta de autenticación, 403 para prohibido, 404 para recurso inexistente y 422 para input semánticamente inválido.

Olvidar la seguridad de reintentos en POST también cuesta caro. Crear pedidos o iniciar pagos puede repetirse tras un timeout. Diseña Idempotency-Key desde el inicio.

La ambigüedad de schema crea bugs lentos. Decide si null significa borrar, desconocido o no enviado. Define campos requeridos, mínimos, límites de paginación, zonas horarias y comportamiento de propiedades extra.

Versionado, errores, schema y seguridad

Versionar no es solo añadir /v1. El equipo necesita reglas compartidas sobre qué rompe clientes. Añadir un campo opcional suele ser seguro; añadir un campo obligatorio, eliminar una respuesta, cambiar un status o endurecer permisos puede ser incompatible.

El diseño de errores debe decir qué hacer después. Invalid request no basta. Usa una forma estable con type, title, status, detail y errors por campo cuando sea útil. No expongas stack traces, SQL ni IDs internos en producción.

El schema debe expresar restricciones, no solo ejemplos. Formato de ID, longitud mínima, límites de array, paginación y fechas reducen la cantidad de suposiciones del cliente.

La seguridad separa autenticación de autorización. Un bearer token no sirve si GET /orders/{id} devuelve pedidos de otro tenant. Evita API keys en query string, reduce datos sensibles, aplica rate limit y guarda logs de auditoría.

CTA de monetización y consultoría

Quien busca diseño de API suele tener un proyecto concreto: publicar una integración, estabilizar un backend móvil o crear reglas de revisión para el equipo. El artículo debe demostrar que el flujo se puede aplicar, no solo explicar teoría.

ClaudeCodeLab puede ayudar con revisión de diseño API usando Claude Code, limpieza de OpenAPI, automatización de pruebas y checks de cambios incompatibles. Los equipos pueden empezar por formación y consultoría; los desarrolladores individuales pueden usar los recursos gratuitos.

Resultado verificado

Probé el código con Node v24.14.1. GET /health devolvió 200, un POST /v1/orders válido devolvió 201 y items vacío devolvió 422. contract-check.mjs falló intencionalmente y mostró el nuevo campo obligatorio y el campo de respuesta eliminado. El flujo completo va de prompts de Claude Code a OpenAPI, Mock, errores y una comprobación útil para CI.

#claude-code #api #rest #openapi #design
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.