Skip to content
routebase
AI AgentsOpenAPIAPI Design

Writing API Descriptions an LLM Can Actually Use

· The Routebase Team

Two matte blocks seated into one another along a machined stepped joint, the seam between them lit

Here is a description that passes every completeness check we know how to write:

/deployments:
  post:
    summary: Create deployment
    description: Creates a new deployment.

Nothing is missing. There is a summary, there is a description, both are non-empty strings. Run it through a style guide and it comes back clean.

Now read it as the only thing you know about this endpoint. What is a deployment here? Can you create one for any environment, or does something have to exist first? Does it return the finished thing, or start something that finishes later? You cannot answer any of that — and neither can an agent, which is why it will call this endpoint with a plausible guess and report success it has no evidence for.

We have written this wrong at least as often as anyone. This is the checklist we now use.

What a linter can see, and where it stops

Routebase ships six built-in rules that look at descriptions: operations, summaries, schemas, responses, parameters, and tags each have one. Every one of them is the same check underneath — is this field empty or whitespace? — and they are graded accordingly. A missing operation description is a warning. A missing parameter description is an information notice.

That is the correct design, and it is worth being precise about why. Presence is machine-checkable; usefulness is not. No rule can decide whether "Creates a new deployment." tells a reader something they did not already know, because answering that requires knowing what the reader already knew. So linting gives you the floor: every description exists. Everything above the floor is a writing problem, and it stays a writing problem no matter how many rules you add.

The trap is that a clean lint report reads like a finished job.

The four questions

A description is doing its job when a reader who has only the contract — no tribal knowledge, no colleague to ask, no staging environment to poke — can answer these:

  1. What does this do, in terms of the domain rather than the mechanics?
  2. When do I use this one instead of its neighbours?
  3. What has to be true first — required state, prior calls, ordering?
  4. What comes back, and what do I do next with it?

Question 2 is the one that gets skipped, and it is the one agents fail on. A human choosing between POST /deployments and POST /config-snapshots reads both, forms a theory, and tries one. An agent is doing the same thing with far less context and no way to notice that its theory was wrong until something downstream breaks.

Applied to the example:

/deployments:
  post:
    summary: Deploy a config snapshot to an environment
    description: >
      Starts a deployment of an existing config snapshot to one environment.
      Create the snapshot first with POST /config-snapshots — this endpoint
      does not capture the current configuration, it only ships a snapshot
      that already exists.
 
      Returns 202 with a deployment id. The deployment is not finished when
      this returns; poll GET /deployments/{id} until status is "succeeded"
      or "failed".

Longer, yes. Long enough to answer all four questions, and no longer — the prose is four sentences.

The most common failure is a description that restates the name

POST /deployments"Creates a new deployment." The method said "creates", the path said "deployments", and the description spent a line saying both again. It costs the reader a line and returns nothing.

We hold our own tooling to this: the rule set behind Routebase's description suggestions says, in as many words, not to repeat the HTTP method or the path, and to describe what the endpoint does rather than how it works internally. Those two instructions kill most bad descriptions on their own.

The reason a restated name is worse than an empty field is that it looks handled. An empty description is visibly a gap, and someone eventually fills it. "Creates a new deployment." closes the ticket.

Descriptions below the operation level

Operation descriptions get the attention. The ones that actually decide whether a call is correct usually sit further down.

Enums. An enum tells a model which values are legal and nothing about what they mean. status: ["pending", "active", "suspended", "closed"] — is a suspended account billable? Can a closed one reopen? If the difference matters to the caller, it belongs in the description, per value.

Parameters that look obvious. limit, since, filter — every API has them and no two agree. What is the maximum for limit, and what happens if you exceed it: clamped, or a 400? Is since inclusive? Answer it once in the parameter description and stop answering it in support threads.

Fields whose name is a lie by omission. email — required or the address the invitation goes to? amount: 1250 — is that $12.50 in cents or $1,250.00 in whole units? The schema says integer, and both readings are type-correct. The description is the only place the unit can live.

The same type-correct field, charged twice. With the description slot empty, nothing says what 1250 counts — the agent reads dollars and moves a hundred times the intended money, while schema and linter stay green in both rows. One sentence in that slot is the difference between $1,250.00 and $12.50: for the unit, the description is the entire contract.

We learned this from the other side of the table. Routebase's own MCP server exposes more than 400 tools, and one convention has earned its keep more than any other: where a parameter takes a fixed set of actions, the description enumerates every legal value, because that description is the only place an agent ever sees them. The parameter is typed as a string; the error thrown on an invalid value repeats the same list. Anything left out of that sentence does not exist as far as the model is concerned.

That generalises past our own tools. The description is not commentary on the contract. For everything the type system cannot express — units, bounds, inclusivity, meaning — it is the contract.

What to leave out

  • Internal implementation. Which service handles it, which queue it lands in, which table it writes. It is noise to a caller and a liability the day it changes.
  • Interface instructions. "Click Save to confirm." There is no Save button in a POST.
  • Restating the schema. The schema already says string, required, maxLength: 200. Do not paraphrase it; say what a valid value means.
  • Marketing. "Our powerful deployment engine." No consumer, human or otherwise, has ever made a better call because of a sentence like this.

How to tell whether it worked

The honest test is cheap: hand the endpoint to an agent with no context beyond the contract and ask it to perform a real task. Watch which endpoint it picks, which parameters it fills, and where it invents a value. Every guess it makes is a sentence you did not write. This is uncomfortable in the specific way good tests are uncomfortable — it finds the gaps you had stopped seeing, because you have known the answers for two years.

Then do the work, which is mostly not thinking. Reading several hundred descriptions, finding the ones that restate their own name, writing the units into the fields that lack them — that is transformation work, and it is what an agent connected to the Routebase MCP server is for: it proposes the rewrite, you accept or reject it, and what lands in the spec is a diff you approved rather than a paragraph you typed. The descriptions your agents will read can be drafted by one, as long as a person still signs off.

None of this is new advice. Descriptions have always been the part of a spec that decides whether anyone can use it without asking you first. What changed is that the reader who cannot ask you is now most of your traffic — and the way you design the rest of the API has to hold up under the same reader.

Ready to ship on it?

Routebase is live. Design your API once — docs, mocks, tests, and monitoring all follow from the same source.

14-day Pro trial — no credit card required.