API Development Patterns
Your POST /orders endpoint double-charges customers when the mobile client retries on a flaky connection. Your cursor pagination silently drops rows when two records share a created_at. And the OpenAPI spec you hand-wrote drifted from the code three sprints ago, so the generated client is lying to every frontend dev who trusts it. These are the API bugs that survive code review and surface in production — and they’re exactly where an AI agent earns its keep, because the fix is a known pattern, not a creative act.
This recipe shows the prompts that turn “the AI scaffolded a route” into “the route is idempotent, paginated correctly, and documented from the source of truth.” We use Express + TypeScript + Zod as the anchor stack, but the prompts port cleanly to Fastify, NestJS, or Next.js Route Handlers.
What You’ll Walk Away With
Section titled “What You’ll Walk Away With”- A paste-ready prompt that generates idempotency-key middleware so client retries never double-charge.
- A cursor-pagination prompt with a stable composite sort that doesn’t drop rows.
- An OpenAPI-3.1-from-handlers prompt that derives the spec from your Zod schemas, so it can’t drift.
- The three-tool workflow for scaffolding an endpoint (Cursor, Claude Code, Codex) and when each shines.
- A “When This Breaks” checklist for the failure modes these AI-generated patterns hit in production.
Scaffolding an endpoint across the three tools
Section titled “Scaffolding an endpoint across the three tools”The first move on any new endpoint is the same: hand the agent your existing route conventions and let it match them. Where the tools differ is how you drive that — inline in the editor, in the terminal, or non-interactively in CI.
Open the router file so it’s in context, then open Agent mode (Cmd/Ctrl + I) and reference the file explicitly:
Using the conventions in
@src/routes/users.ts, add aPOST /ordersroute to@src/routes/orders.ts. Validate the body with a Zod schema (items: array of {sku, qty},idempotencyKey: uuid), return201with the created order, and409on a duplicate idempotency key. Wire it into the existing error-handling middleware — do not invent new error classes.
Agent mode edits the file in place and shows a diff per file. Use Checkpoints before accepting so you can roll back the whole change if the validation shape is wrong.
From the repo root, give Claude Code the same instruction and let it read the conventions itself:
claude "Read src/routes/users.ts for our route conventions, then add a POST /orders \route in src/routes/orders.ts. Validate the body with Zod (items, idempotencyKey:uuid), \return 201 on success and 409 on duplicate key, and reuse the existing error middleware."Claude Code uses its Read and Edit tools to match your patterns. Add --permission-mode plan first if you want it to propose the change before touching files.
Use codex exec for a scripted, non-interactive run — ideal when you want the change to land as a single reviewable commit:
codex exec --ask-for-approval on-request \"Add a POST /orders route in src/routes/orders.ts following the conventions in \src/routes/users.ts. Zod-validate the body (items, idempotencyKey), return 201/409, \and reuse the existing error middleware."For interactive work, drop the exec and run codex in the TUI. Codex Cloud can run the same task in an isolated worktree and open a PR — useful when the endpoint touches migrations you want reviewed in isolation.
Idempotent endpoints: the double-charge fix
Section titled “Idempotent endpoints: the double-charge fix”A retried POST is the classic distributed-systems trap. The fix is an idempotency-key middleware that records the first response and replays it for any repeat of the same key. This is mechanical enough that the AI gets it right — if you tell it where to store the key and how long to keep it.
When the AI hands this back, audit two things before you trust it. First, the in-flight lock: a naive version stores the response only after the handler finishes, so two simultaneous retries both miss the cache and both charge. The replay entry must be claimed (e.g. SET idem:{key} "processing" NX) before the handler runs. Second, the TTL vs. business window: a 24h TTL means a retry on day two double-charges anyway. Ask the AI to make the window explicit and matched to your client’s retry policy.
Cursor pagination that doesn’t drop rows
Section titled “Cursor pagination that doesn’t drop rows”Offset pagination (?page=3&limit=20) is fine for an admin table but breaks under writes: insert a row while a user pages and everything shifts. Cursor pagination is the production answer, and the bug AI models reliably introduce is sorting on a non-unique column. If two orders share a created_at, a cursor on created_at alone will skip or repeat rows at the page boundary.
The tell that the AI did this right is the composite predicate. WHERE created_at < $1 is wrong; WHERE (created_at, id) < ($1, $2) is right. If you see OFFSET anywhere in the generated query, reject it — that’s the bug you came here to avoid.
OpenAPI from the source of truth
Section titled “OpenAPI from the source of truth”Hand-maintained OpenAPI drifts. The durable fix is to derive the spec from the same Zod schemas your handlers already validate against, so the contract and the runtime can’t disagree. Libraries like @asteasolutions/zod-to-openapi make this a generation step rather than a parallel document.
The CI staleness check is the part that makes this stick. Without it, the spec regenerates only when someone remembers, and you’re back to drift. With it, a schema change that doesn’t regenerate the doc fails the build.
When This Breaks
Section titled “When This Breaks”What’s Next
Section titled “What’s Next”- Database Development Patterns — the query-optimization and zero-downtime-migration prompts behind these endpoints.
- Serverless patterns for deploying these APIs to the edge.