
Designing REST APIs LLMs Can Actually Use: A Field Guide for Tool-Using Agents
The API design choices that make or break tool-using LLM agents — naming, error shapes, idempotency, discoverability, JSON schema hygiene, pagination, and the eight anti-patterns that confuse every model.
LLMs can call any HTTP API — but they call some APIs correctly on the first try and fail loops of ten on others. The difference isn't the model; it's the API design. This guide covers the concrete design rules that make an API 'agent-friendly': explicit naming, structured error shapes with actionable hints, mandatory idempotency keys on writes, JSON Schema that constrains rather than describes, cursor-based pagination, and OpenAPI specs that a tool router can consume without a human translator. If your APIs will be called by agents, treat this as a compliance checklist.
- Use full, explicit names (create_customer, not POST /c).
- Return errors with a machine-readable code, a human message, and a next-action hint.
- Require idempotency keys on every write; agents retry constantly.
- Use JSON Schema enums and formats to constrain — LLMs respect constraints.
- Cursor pagination, not offset; agents lose track of page numbers.
- Ship an OpenAPI 3.1 spec with descriptions written for humans, not just machines.
- One resource per endpoint; polymorphic endpoints confuse tool routers.
Why LLMs fail at 'normal' APIs
Every API team has a war story: they wired up their perfectly reasonable REST API to a tool-using agent, and the agent looped, hallucinated fields, called the wrong endpoint, or gave up. Then they blamed the model.
The model isn't the problem. Most APIs were designed for human developers who read docs, remember conventions, and have taste. Agents have none of those. They see a JSON schema and a description; they act on what those literally say.
Designing 'agent-friendly' APIs means removing every place where implicit knowledge is required. It also — happily — makes the API better for humans.
Rule 1 — Explicit, complete names
Short URLs are a legacy of typing on terminals. Agents don't type. Longer, explicit names reduce ambiguity dramatically.
| Bad | Good |
|---|---|
| POST /c | POST /customers with operation_id: create_customer |
| GET /o/:id/s | GET /orders/{order_id}/shipments |
| PATCH /u | PATCH /users/{user_id} |
Operation IDs in your OpenAPI spec become the tool names an agent sees. Make them read like verbs an intern would use: list_pending_invoices, not list_inv_p.
Rule 2 — Errors with actionable next steps
Every error should let the agent decide what to do next: retry, ask the user, or give up. That means three fields:
{
"error": {
"code": "customer_email_taken",
"message": "A customer with this email already exists.",
"next_action": "fetch_existing_customer",
"next_action_params": { "email": "ada@example.com" },
"retryable": false,
"docs_url": "https://api.example.com/docs/errors/customer_email_taken"
}
}retryable: true retry with backoff. Agents that see next_action chain calls without human help. This one field pair cuts agent latency by 30–60% on error paths.Rule 3 — Idempotency keys are mandatory, not optional
Agents retry. Networks fail. The user says 'try again'. Without an idempotency key, you get duplicate charges, duplicate emails, duplicate tickets.
Accept an Idempotency-Key header on every non-GET endpoint. Cache the response for 24h keyed by (endpoint, key, request-body-hash). Return the same response on retry.
Same pattern for webhooks going the other way — see webhooks that don't lie.
Rule 4 — JSON Schema that constrains
LLMs respect JSON Schema. Use it aggressively to prevent bad calls:
- Use
enumfor finite value sets — do not accept free-text where enums exist. - Use
format: 'email',format: 'date-time',patternfor regex constraints. - Mark fields
requiredwhen they truly are; agents skip optional fields aggressively. - Add
descriptionon every field, in prose — this is what the model reads to know what the field means. - Use
oneOffor tagged unions with a discriminator; avoidanyOfwhich is ambiguous.
Rule 5 — Cursor pagination, not offset
Agents lose track of page numbers across turns. Cursors are self-describing:
{
"items": [...],
"next_cursor": "eyJpZCI6MTIzNCwidHMiOiIyMDI2LTA3LTA4In0",
"has_more": true
}The agent just passes cursor back in the next call. No off-by-one, no state to remember, no double-fetching.
Rule 6 — One resource, one endpoint
Polymorphic endpoints — POST /entities that accepts 12 different type values — confuse tool routers. Split into create_customer, create_invoice, create_ticket. Yes, it's more endpoints. Yes, it's better.
Rule 7 — OpenAPI 3.1, written for agents
Your OpenAPI spec is the contract every tool-using framework reads. Treat descriptions as first-class UX:
paths:
/customers:
post:
operationId: create_customer
summary: Create a new customer record.
description: |
Creates a customer. Returns 409 if a customer with the same email
already exists — in that case, call get_customer_by_email instead
of retrying.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CustomerCreate" }Tools like Chat GPT plugins, Anthropic tool use, and LangGraph ingest this spec verbatim. Bad descriptions == bad agent behavior.
Rule 8 — Rate limits with a hint, not a hammer
Return 429 with headers the agent can act on:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1720431600
Retry-After: 12Agents will honor Retry-After automatically. Without it, they hammer you.
Eight anti-patterns to fix today
| Anti-pattern | Fix |
|---|---|
| Free-text status field | Enum with 3–8 values |
| Booleans that should be enums (paid: true/false → status) | Enum: unpaid/paid/refunded/void |
| Timestamps as unix ints | ISO 8601 with timezone |
| Errors as HTML pages | Structured JSON always |
| POST for reads | Use GET; caching + LLM heuristics rely on verb |
| Field names in multiple cases (userId, user_name) | Pick snake_case, apply everywhere |
| Silent truncation of long inputs | 413 with max length |
| Deprecated endpoints still 200 OK | 410 Gone + successor endpoint hint |
Testing your API with an actual agent
Before shipping API changes, run a smoke test with a real tool-using agent:
- Feed the OpenAPI spec into your agent framework.
- Give the agent a natural-language task ("create a customer named Ada with email x, then create an invoice for $100").
- Log every call, every retry, every error.
- Any confusion the agent shows is a doc bug or design bug — fix it, not the prompt.
Deeper eval methodology in evaluating LLMs.
The compounding win
APIs designed this way don't just work better for agents — they're easier for humans too. Better error messages, cleaner names, stricter schemas, and cursor pagination are best practices from long before LLMs existed. The pressure of tool-using agents just makes the tax on bad APIs finally visible.
Building something similar?
I help teams ship production-grade AI agents, n8n workflows, and data platforms. Let's talk about what you're building.
Work with me
Islam Gamal
AI, Data & Automation Engineer. I design and ship production AI agents, n8n workflows, and cloud data platforms — with a focus on reliability, cost, and measurable business impact. Founder of Tashghil and Tek bil Arabi.
More from Islam Gamal
Building Production AI Agents with n8n: Architecture, Guardrails, Evals, and Cost Control
The full n8n agent playbook — reference architecture, memory design, tool routing, JSON-schema guardrails, retries, DLQs, evals, observability, and the small handful of decisions that keep the token bill sane at scale.
Prompt Engineering for Agents: A Structured, Testable, Version-Controlled Approach
Prompt engineering isn't vibes. Treat prompts like code — with schemas, versions, evals, diffs, and rollback. The full production workflow that scales past a single developer, with copy-paste patterns for structured output, few-shot design, LLM-as-judge, and prompt registries.
Webhooks That Don't Lie: Idempotency, Signatures, Replay, and a Playbook
An opinionated production guide to receiving webhooks — signature verification, idempotency keys, store-then-process pipelines, dead-letter queues, replay UIs, provider quirks, and a local testing loop your team will actually use.
Browse every article by Islam Gamal on the author page.