Tips & Tricks (Actualizado: 3/6/2026)

Diseño de REST API con Claude Code: endpoints, errores, paginación e idempotencia

Diseña REST APIs con Claude Code antes de programar: OpenAPI, errores, paginación, idempotencia y checklist.

Diseño de REST API con Claude Code: endpoints, errores, paginación e idempotencia

Una REST API no está terminada solo porque existan unas cuantas rutas. Antes de implementar, conviene decidir qué será un recurso, qué método HTTP usará cada operación, qué devolverá la API cuando falle, cómo paginará las listas y qué ocurrirá si recibe la misma petición dos veces. Si esas decisiones quedan abiertas, Claude Code puede escribir código rápido, pero el frontend, la app móvil y las integraciones externas acabarán interpretando contratos distintos.

El objetivo de este artículo es usar Claude Code para cerrar el diseño de la REST API antes de tocar la implementación. REST significa Representational State Transfer, pero para trabajar en producto basta con verlo como un conjunto de reglas para operar recursos como pedidos, usuarios o facturas a través de HTTP. El recurso es el objeto, el método es la operación y el código de estado es la señal del resultado.

Como referencias oficiales, usa HTTP request methods y HTTP response status codes de MDN, además de la OpenAPI Specification. A fecha 3 de junio de 2026, la página latest de OpenAPI apunta a 3.2.0; el ejemplo usa openapi: 3.1.0 porque muchas herramientas de lint y generación todavía lo soportan de forma más predecible.

Para profundizar en implementación, revisa desarrollo de APIs con Claude Code, testing de APIs con Claude Code y code review con Claude Code. Si quieres plantillas y checklists reutilizables, están en /products/. Para incorporar revisiones de diseño de API en tu equipo, consulta /training/.

Alinear El Vocabulario Primero

Quien empieza suele confundirse porque los términos de REST se parecen. Antes de pedir implementación a Claude Code, conviene que el equipo use estas palabras con el mismo significado.

TérminoExplicación sencillaEjemplo
RecursoEl sustantivo que maneja la APIorders, customers
EndpointLa combinación de URL y método HTTPGET /v1/orders
MétodoEl verbo HTTP que indica la operaciónGET, POST, PATCH
Código de estadoNúmero que comunica éxito, error de entrada, falta de autenticación, etc.200, 201, 400, 404
ValidaciónComprobación de que la petición tiene la forma correctaFormato de email, cantidad mínima
IdempotenciaPropiedad por la que repetir la misma operación no cambia el estado finalCrear un solo pedido para la misma clave

Claude Code entiende contexto, pero no puede garantizar que adivine tu intención de diseño. “Crea la API para pedidos” puede convertirse en /createOrder, /orders/create o POST /orders. Pasar vocabulario y reglas antes del código mejora mucho el resultado.

Decidir Con Tres Casos De Uso

La misma API de pedidos cambia según cómo se consuma.

El primer caso es una integración B2B SaaS. Si un partner reintenta el mismo CSV nocturno, POST /v1/orders necesita Idempotency-Key para no crear pedidos duplicados. Los reintentos aparecen durante incidentes de red, así que la idempotencia es difícil de añadir bien al final.

El segundo caso es una lista de administración. Una persona filtra por status=paid, avanza a la siguiente página y otra persona crea un pedido mientras tanto. page=2 puede desplazarse. Es mejor usar paginación por cursor con limit y after, y fijar el orden, por ejemplo createdAt desc, id desc.

El tercer caso es una API para aplicación móvil. Si las versiones antiguas permanecen instaladas durante meses, no puedes cambiar nombres de campos de respuesta de golpe. Nuevos campos obligatorios o un nuevo formato de error deben ir a /v2, mientras el contrato de /v1 sigue en OpenAPI. Añadir campos opcionales normalmente no exige nueva versión.

Un cuarto caso habitual son APIs internas entre servicios. Es más fácil actualizar consumidores a la vez, pero el monitoreo suele ser más débil. Incluso dentro de la empresa, códigos de estado y errores inconsistentes obligan a cada consumidor a escribir excepciones propias.

Ver El Flujo De Diseño

Fija el diseño en este orden antes de entregar la tarea a Claude Code. Así la implementación se mantiene cerca del contrato mientras se editan archivos.

flowchart TD
  A["Use cases"] --> B["Resources and endpoints"]
  B --> C["OpenAPI contract"]
  C --> D["Errors, pagination, idempotency"]
  D --> E["Claude Code implementation"]
  E --> F["Tests and review"]

Fijar La Tabla De Endpoints Antes De Programar

El primer entregable no es código, sino una tabla de endpoints. Entrégala a Claude Code para que rutas, OpenAPI, pruebas y documentación salgan del mismo contrato.

PropósitoMétodo y rutaÉxitoRegla importante
Listar pedidosGET /v1/orders?status=paid&limit=20&after=ord_123200 OKPaginación por cursor con orden estable
Crear pedidoPOST /v1/orders201 CreatedAceptar Idempotency-Key y devolver Location
Ver pedidoGET /v1/orders/{orderId}200 OKSi no existe, devolver 404
Actualizar pedidoPATCH /v1/orders/{orderId}200 OKActualización parcial; body vacío es 400
Cancelar pedidoPOST /v1/orders/{orderId}/cancel200 OKTratar transiciones de estado de forma consistente

La regla general es “sustantivos en la URL, verbos en el método”. Algunas acciones de negocio, como cancelar un pedido, se entienden mejor como subacción explícita que forzándolas a DELETE. Lo importante es documentar la excepción y mantener el patrón en operaciones parecidas.

Usar OpenAPI Como Fuente Del Diseño

OpenAPI describe rutas, parámetros, peticiones y respuestas en un formato que las herramientas pueden leer. Si indicas a Claude Code que este contrato OpenAPI es la fuente de verdad, reduces la libertad de implementación al espacio correcto.

openapi: 3.1.0
info:
  title: Acme Orders API
  version: 1.0.0
servers:
  - url: https://api.example.com
paths:
  /v1/orders:
    get:
      operationId: listOrders
      summary: List orders
      parameters:
        - $ref: "#/components/parameters/StatusParam"
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/AfterParam"
      responses:
        "200":
          description: Orders are returned in a stable cursor order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
    post:
      operationId: createOrder
      summary: Create an order
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrderRequest"
      responses:
        "201":
          description: Order created.
          headers:
            Location:
              schema:
                type: string
              description: URL of the created order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          $ref: "#/components/responses/Conflict"
  /v1/orders/{orderId}:
    get:
      operationId: getOrder
      summary: Get one order
      parameters:
        - $ref: "#/components/parameters/OrderId"
      responses:
        "200":
          description: Order found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderResponse"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      operationId: updateOrder
      summary: Update mutable order fields
      parameters:
        - $ref: "#/components/parameters/OrderId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOrderRequest"
      responses:
        "200":
          description: Order updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
components:
  parameters:
    OrderId:
      name: orderId
      in: path
      required: true
      schema:
        type: string
    StatusParam:
      name: status
      in: query
      schema:
        type: string
        enum: [draft, paid, canceled]
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    AfterParam:
      name: after
      in: query
      schema:
        type: string
      description: Cursor returned by the previous response.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        minLength: 16
        maxLength: 128
  schemas:
    CreateOrderRequest:
      type: object
      required: [customerId, items]
      properties:
        customerId:
          type: string
        items:
          type: array
          minItems: 1
          items:
            type: object
            required: [sku, quantity]
            properties:
              sku:
                type: string
              quantity:
                type: integer
                minimum: 1
    UpdateOrderRequest:
      type: object
      minProperties: 1
      properties:
        status:
          type: string
          enum: [draft, paid, canceled]
    OrderResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Order"
    OrderListResponse:
      type: object
      required: [data, pageInfo]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Order"
        pageInfo:
          type: object
          required: [nextCursor, hasMore]
          properties:
            nextCursor:
              type: [string, "null"]
            hasMore:
              type: boolean
    Order:
      type: object
      required: [id, status, totalCents, createdAt]
      properties:
        id:
          type: string
        status:
          type: string
        totalCents:
          type: integer
        createdAt:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, requestId]
          properties:
            code:
              type: string
            message:
              type: string
            requestId:
              type: string
            details:
              type: array
              items:
                type: object
  responses:
    BadRequest:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Conflict:
      description: Idempotency key conflicts with a different request.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Resource was not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

Guárdalo como openapi.yaml y pide a Claude Code que no proponga implementaciones que se aparten del contrato. Después, una herramienta como npx @redocly/cli lint openapi.yaml puede validar sintaxis y referencias. Si generas tipos o clientes TypeScript desde OpenAPI, revisa antes el significado humano de la tabla.

Unificar Errores Y Códigos De Estado

El código de estado es la señal HTTP externa. El JSON de error contiene el detalle que leen personas y programas. Devolver 200 OK con { "success": false } hace que monitoreo, SDKs y reintentos traten el fallo como éxito. Fija una tabla: entrada inválida es 400, sin autenticación es 401, sin permisos es 403, recurso inexistente es 404, duplicado o conflicto de idempotencia es 409.

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Request body has invalid fields.",
    "requestId": "req_01J0RESTAPI001",
    "details": [
      {
        "field": "items[0].quantity",
        "reason": "must be greater than or equal to 1"
      }
    ]
  }
}
{
  "error": {
    "code": "IDEMPOTENCY_CONFLICT",
    "message": "The same Idempotency-Key was used with a different request body.",
    "requestId": "req_01J0RESTAPI002"
  }
}
{
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "Order was not found.",
    "requestId": "req_01J0RESTAPI003"
  }
}

Para principiantes: code es el nombre corto para que el programa decida una rama, message es la explicación para logs o interfaz, y requestId permite encontrar los logs del servidor. details solo hace falta cuando hay errores de validación por campo.

No Dejar Ambiguas La Paginación, La Idempotencia Ni La Versión

Las APIs de lista necesitan paginación desde el inicio. Devolver todos los pedidos funciona con pocos datos, pero cuando la tabla crece cambiar la respuesta se convierte en breaking change. Con paginación por cursor, el cliente envía el nextCursor anterior como nuevo after.

{
  "data": [
    {
      "id": "ord_100",
      "status": "paid",
      "totalCents": 4800,
      "createdAt": "2026-06-03T09:00:00Z"
    }
  ],
  "pageInfo": {
    "nextCursor": "ord_099",
    "hasMore": true
  }
}

La idempotencia evita que peticiones repetidas creen efectos duplicados. POST /orders es especialmente delicado porque los clientes reintentan tras fallos de red. Guarda Idempotency-Key y un hash del body durante un tiempo limitado. Si llega la misma clave con el mismo body, devuelve el resultado anterior; si llega la misma clave con otro body, devuelve 409.

El versionado da tiempo de migración a los consumidores. Quitar un campo obligatorio, cambiar un tipo, modificar el orden por defecto o añadir un campo obligatorio al request es breaking change y debe ir a /v2. Añadir un campo opcional normalmente puede quedarse en /v1.

Pedir A Claude Code Una Revisión De Diseño

Antes de implementar, usa un prompt concreto.

Review this OpenAPI design.
Check:
- Resource names are nouns
- HTTP methods and status codes match MDN semantics
- 400/401/403/404/409/500 errors share the same JSON shape
- List pagination is unambiguous
- POST creation endpoints that need idempotency keys are identified
- No response change breaks the v1 contract
- Return a table of issues to fix before TypeScript implementation and tests

Así Claude Code actúa primero como revisor de diseño, antes de editar archivos. Pide la implementación solo después de guardar el OpenAPI revisado.

Errores Frecuentes

El primer error es crear endpoints con estilo de acción. Si conviven POST /createOrder, POST /updateOrderStatus y GET /deleteOrder, la URL ya no comunica recurso y operación. Prefiere POST /v1/orders, PATCH /v1/orders/{orderId} y DELETE /v1/orders/{orderId}, y documenta excepciones.

El segundo error es mezclar códigos de estado. Si una entrada inválida devuelve 500, un recurso inexistente devuelve 200 y una creación exitosa alterna entre 200 y 201, los clientes no pueden tomar decisiones fiables. Estandariza también los códigos de éxito.

El tercer error es paginación ambigua. Si page=2 no define si el orden es por creación o actualización, los datos nuevos causan duplicados o saltos. Documenta orden, limit máximo y cómo detectar la siguiente página.

El cuarto error es esconder validación solo en la implementación. Si quantity >= 1 está en el código pero no en OpenAPI, el frontend y los SDK generados no pueden reutilizar la regla. Las restricciones deben vivir en la especificación y en el código.

Checklist De Revisión

  • Los recursos usan sustantivos plurales de forma consistente
  • Los métodos respetan el significado de GET, POST, PATCH y DELETE
  • Los éxitos distinguen 200, 201 y 204
  • 400, 401, 403, 404, 409 y 500 comparten una forma JSON
  • Las listas incluyen limit, after, nextCursor y hasMore
  • limit tiene valor por defecto y máximo documentados
  • Los endpoints de creación no duplican datos al reintentar
  • Los criterios para mover breaking changes a /v2 están escritos
  • Las reglas de validación aparecen en OpenAPI
  • La tabla de endpoints y OpenAPI coinciden antes de pasar la tarea a Claude Code

Diseñar una REST API no consiste en hacer URLs bonitas. Consiste en convertir promesas para los consumidores en texto legible y especificaciones que las herramientas puedan leer. Claude Code acelera la implementación, pero las promesas que el sistema debe cumplir las decide el equipo.

Al probarlo en la API de pedidos de Masa, preparar primero OpenAPI, la tabla de endpoints y los JSON de error redujo de forma clara el retrabajo en revisión. Incluir Idempotency-Key y paginación por cursor desde el diseño evitó parches posteriores para pedidos duplicados y registros omitidos en las listas.

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