REST API Design with Claude Code: Endpoints, Errors, Pagination, Idempotency
Design REST APIs with Claude Code before coding: OpenAPI, errors, pagination, idempotency, and review checks.
A REST API is not finished just because you created a few routes. Before implementation, decide what counts as a resource, which HTTP method each action uses, what the API returns when it fails, how lists are paginated, and how repeated requests are handled. If those choices stay vague, Claude Code can still write code quickly, but frontend, mobile, and partner integrations will each infer a different contract.
This article focuses on using Claude Code to lock the REST API design before implementation. REST stands for Representational State Transfer, but for practical work you can read it as a rule set for operating on resources such as orders, users, and invoices over HTTP. A resource is the thing you handle, a method is the operation, and a status code is the result signal.
For official references, use MDN’s HTTP request methods and HTTP response status codes, plus the OpenAPI Specification. As of June 3, 2026, the OpenAPI latest page resolves to 3.2.0; the sample below uses openapi: 3.1.0 because many linters and generators still handle it more predictably.
For related implementation work, read production API development with Claude Code, Claude Code API testing, and Claude Code code review. If you want reusable templates and checklists, see /products/. If you want to standardize API design reviews across a team, see /training/.
Align The Vocabulary First
Beginners often struggle because REST API design terms sound similar. Before asking Claude Code to implement anything, make sure the team uses these words consistently.
| Term | Plain explanation | Example in this article |
|---|---|---|
| Resource | The noun the API handles; the target of create, read, update, and delete work | orders, customers |
| Endpoint | A URL plus an HTTP method for entering a resource | GET /v1/orders |
| Method | The HTTP verb that says what you want to do | GET, POST, PATCH |
| Status code | A numeric signal for success, bad input, missing auth, and so on | 200, 201, 400, 404 |
| Validation | Checks that the request has the right shape | Email format, minimum quantity |
| Idempotency | A property where repeated identical operations reach the same final state | Creating one order for the same key |
Claude Code is good at reading context, but it cannot guarantee the design meaning you had in mind. A request like “build the order creation API” could become /createOrder, /orders/create, or POST /orders. Passing vocabulary and design rules first is the simplest way to improve the generated implementation.
Decide Through Three Use Cases
The same orders API has different design priorities depending on how it is used.
The first use case is B2B SaaS order ingestion. If a partner retries the same nightly CSV job, POST /v1/orders needs an Idempotency-Key so the same request does not create duplicate orders. Retry behavior appears during incident recovery, so idempotency is hard to bolt on later.
The second use case is an admin list API. A staff member may filter by status=paid, move to the next page, and another staff member may add an order at the same time. Plain page=2 can drift. Use cursor pagination with limit and after, and fix the sort order, for example createdAt desc, id desc.
The third use case is a mobile app API. If old app versions stay installed for months, response field names cannot change abruptly. New required fields or a new error envelope should become /v2, while the /v1 contract remains in OpenAPI. Small optional response fields usually do not require a new version.
A fourth common case is an internal service API. It is easier to upgrade all consumers, but monitoring and alerts are often weaker. Even internally, inconsistent status codes and error shapes force every caller to write custom exception handling.
See The Design Flow
Freeze the design in this order before giving the task to Claude Code. It keeps the implementation from drifting while files are being edited.
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"]
Freeze The Endpoint Table Before Coding
The first deliverable is not code. It is an endpoint table. Give this table to Claude Code so routing, OpenAPI, tests, and docs all start from the same contract.
| Purpose | Method and path | Success | Important rule |
|---|---|---|---|
| List orders | GET /v1/orders?status=paid&limit=20&after=ord_123 | 200 OK | Cursor pagination with a stable sort |
| Create order | POST /v1/orders | 201 Created | Accept Idempotency-Key and return Location |
| Get order | GET /v1/orders/{orderId} | 200 OK | Return 404 when missing |
| Update order | PATCH /v1/orders/{orderId} | 200 OK | Partial update; empty patch is 400 |
| Cancel order | POST /v1/orders/{orderId}/cancel | 200 OK | Treat business state transitions consistently |
The usual rule is “nouns in URLs, verbs in methods.” Some business actions, such as canceling an order, are clearer as an explicit sub-action than as a forced DELETE. The key is to write the exception down and keep the same pattern for the same kind of operation.
Make OpenAPI The Design Source
OpenAPI is a machine-readable design document for paths, parameters, requests, and responses. When you tell Claude Code “implement this OpenAPI contract as the source of truth,” you narrow the implementation freedom to the right area.
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"
Save this as openapi.yaml and tell Claude Code not to propose implementation that diverges from it. After generation, a linter such as npx @redocly/cli lint openapi.yaml can check syntax and references. If you generate TypeScript types or clients from OpenAPI, review the human meaning of the endpoint table before generating code.
Standardize Errors And Status Codes
The status code is the outer HTTP signal. The JSON body is the detail that humans and programs read. Returning 200 OK with { "success": false } makes monitoring, SDKs, and retry logic treat the failure as success. Fix a table first: invalid input is 400, missing authentication is 401, insufficient permission is 403, a missing resource is 404, and duplication or idempotency conflict is 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"
}
}
For beginners: code is the short name a program can branch on, message is the explanation a person reads in logs or UI, and requestId is the identifier support uses to find server logs. Add details only when validation errors need field-level explanations.
Do Not Leave Pagination, Idempotency, Or Versioning Vague
List APIs need pagination from the start. Returning every order may work while data is tiny, but once the table grows, changing the response becomes a breaking change. With cursor pagination, the client passes the previous response’s nextCursor as the next after value.
{
"data": [
{
"id": "ord_100",
"status": "paid",
"totalCents": 4800,
"createdAt": "2026-06-03T09:00:00Z"
}
],
"pageInfo": {
"nextCursor": "ord_099",
"hasMore": true
}
}
Idempotency prevents repeated requests from creating duplicate effects. POST /orders is especially vulnerable because clients retry after network failures. Store the Idempotency-Key and a hash of the request body for a limited time. If the same key and same body arrive again, return the previous result. If the same key arrives with a different body, return 409.
Versioning gives consumers migration time. Removing a required response field, changing a type, changing the default sort order, or adding a new required request field is a breaking change and should move to /v2. Adding an optional response field usually can stay in /v1.
Prompt Claude Code For Design Review
Use a concrete review prompt before implementation.
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
This makes Claude Code act as a design reviewer before it edits files. Ask for implementation only after you save the reviewed OpenAPI.
Common Failure Cases
Failure case 1 is action-style endpoints. If POST /createOrder, POST /updateOrderStatus, and GET /deleteOrder coexist, the URL no longer tells readers the resource and operation clearly. Prefer POST /v1/orders, PATCH /v1/orders/{orderId}, and DELETE /v1/orders/{orderId}, then document exceptions.
Failure case 2 is inconsistent status codes. If bad input returns 500, missing resources return 200, and successful creation alternates between 200 and 201, clients cannot branch reliably. Standardize success codes as well as error codes.
Failure case 3 is ambiguous pagination. If page=2 does not define whether the order is by creation time or update time, new data can cause duplicates or skipped rows. Document sort order, max limit, and how to detect the next page.
Failure case 4 is hiding validation inside implementation only. If the code has quantity >= 1 but OpenAPI does not, frontend code and generated SDKs cannot reuse the rule. Put constraints in both the specification and the implementation.
Review Checklist
- Resource names use plural nouns consistently
- Methods match
GET,POST,PATCH, andDELETEsemantics - Success codes distinguish
200,201, and204 400,401,403,404,409, and500errors share one JSON shape- List APIs include
limit,after,nextCursor, andhasMore - Default and maximum
limitvalues are documented - Create endpoints cannot duplicate data during retries
- Criteria for moving breaking changes to
/v2are written down - Validation rules appear in OpenAPI
- The endpoint table and OpenAPI agree before Claude Code receives the task
REST API design is not about making pretty URLs. It is about turning promises to consumers into readable text and machine-readable specifications. Claude Code can increase implementation speed, but humans still need to decide which promises the system must keep.
When Masa tried this on an orders API, putting the OpenAPI file, endpoint table, and error JSON in place before asking Claude Code to implement clearly reduced review rework. Adding Idempotency-Key and cursor pagination during design also avoided later patches for duplicate orders and missing list records.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Safety Ladder: Expand Access Without Losing Control
A beginner-friendly ladder for moving Claude Code from read-only to limited edits, proof commands, and deploy checks.
Claude Code Small PR Proof Pack: Make Tiny Changes Reviewable
A practical proof pack for Claude Code PRs: diff, checks, public URL, CTA path, and rollback note.
Claude Code Review Gate Before Commit: Diff, Tests, Public URL, and CTA Checks
A commit-time review gate for Claude Code work: diff scope, build, public URL, revenue CTA links, missing tests, and unrelated files.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.