API strategy

Platform integration

OpenAPI specification best practices: writing specs agents and SDK generators can actually use

OpenAPI specification best practices for teams whose specs are now read by SDK generators and AI agents, not just humans. What changes and how to keep up.

9 minute read
Decorative imagery showcasing Pontil's brand

The OpenAPI specification has become the default contract for HTTP APIs. Most teams have one. Far fewer have a spec that holds up when something downstream — an SDK generator, an agent tool definition, a documentation site, a CI breaking-change check — tries to use it as the source of truth.

The view here is direct: an OpenAPI spec is only useful to the degree downstream tooling can consume it without a human translator. If your spec needs a follow-up Slack message to explain what a field means, it isn't a contract. It's a draft.

This deep-dive covers what changes when agents and SDK generators read your spec, the structural rules that make specs machine-usable, how to document behaviour the schema can't express, how to keep the spec honest as the API evolves, and where even a perfect spec stops being enough. It's aimed at platform teams treating OpenAPI as production infrastructure, not as a documentation byproduct.

Why OpenAPI specs that worked for humans break for agents and generators

For a decade, the implicit consumer of an OpenAPI spec was a human developer reading Swagger UI or Redoc. Humans tolerate ambiguity. They read the prose description, infer that status is really an enum even though it's typed as string, and figure out that the id in the response is the same id you pass to the next endpoint. The spec was a starting point, not a contract.

Agents and SDK generators don't have that latitude. An SDK generator like Stainless, Fern, or Speakeasy turns your spec directly into typed code. If a field is typed string but is actually one of four values, the generated SDK has a string parameter and customers pass invalid values until they get a 400. If a response shape is described in prose but not in the schema, the generated types don't include it.

Agents have the same problem, with sharper edges. When a foundation model from Anthropic or OpenAI reads a tool definition derived from your spec, it uses the schema — types, enums, descriptions, required fields — to decide what arguments to pass. Vague descriptions produce vague tool calls. Missing enums produce hallucinated values. Optional fields with no description get omitted when they shouldn't be. We've written about this access gap more broadly in API products are not the same as agent-ready products — the underlying issue is the same. A spec built for human readers leaves machines guessing.

The shift is simple. Your spec is now read by code more often than by people. Best practices have to assume that.

Structure the spec so generators and agents can read it without context

Most of what makes a spec machine-usable is structural. Five rules cover the majority of the problem.

Use operationId everywhere, and make them stable

operationId is how SDK generators name methods (client.invoices.create) and how agent tool definitions get named. If you don't set one, generators fall back to deriving names from paths and methods, which produces things like post_v1_invoices and changes the moment you reorganise routes.

Rules: set operationId on every operation. Use camelCase or snake_case consistently. Make it semantic (createInvoice, not postInvoices). Treat it as part of your public contract — renaming it is a breaking change for every generated SDK.

Type fields precisely, and use enums for closed sets

A field typed string with a description that lists four allowed values is not typed. Use enum for closed sets. Use format: date-time, format: uuid, format: email where applicable. Use minimum, maximum, pattern where the value is constrained.

The payoff is concrete. An SDK generator turns enums into typed unions or language enums. An agent reading the tool definition knows which four values are valid and won't invent a fifth. A breaking-change detector flags it when you add a new enum value (which is a breaking change for strictly-typed consumers — see How to detect API breaking changes).

Use $ref and components aggressively

Duplication in a spec is where drift starts. Define each object once in components/schemas and reference it everywhere. Same for parameters (components/parameters), responses (components/responses), and security schemes.

The practical test: if you change the shape of a User object, you should edit one place in the spec, not seventeen.

Mark required fields honestly

required on an object schema isn't optional metadata. SDK generators use it to decide whether a parameter is required in the method signature. Agents use it to decide whether they need to ask the user for a value or can omit it. Get this wrong and you either force callers to supply values that don't exist, or let them omit values that produce 400s.

The rule: a field is required only if the API will reject the request without it. Default to optional and add required deliberately.

Define error responses, not just success

Most specs document the 200. Many document a generic 400. Few document the actual error shapes — what fields the error body contains, what the error codes mean, what a 409 versus a 422 actually indicates.

This matters more for agents than for SDKs. When an agent calls a tool and gets back { "error": "invoice_already_finalized" }, it needs to know that error exists, what it means, and that retrying won't help. A spec that says only 400: Bad Request gives the agent nothing to reason about. Define error response schemas. Enumerate error codes. Describe what each one means.

Document behaviour the schema can't capture — but in the spec

A schema describes shape. It doesn't describe behaviour. Three areas where behaviour matters and where the spec is the right place to document it:

Idempotency. Which endpoints are safe to retry? Which require an idempotency key? If POST /payments is idempotent only when you supply an Idempotency-Key header, say so in the operation description, define the header as a parameter, and document the behaviour when it's missing. This is the difference between an SDK that adds automatic retries safely and one that double-charges customers.

Pagination. If your list endpoints paginate, the spec should describe the mechanism (cursor, offset, page-token), the parameter names, and the response shape (where the next cursor lives, how the consumer knows there are no more pages). SDK generators can produce automatic pagination helpers when the pattern is documented consistently. Agents can iterate through pages without re-discovering the pattern each time.

Rate limits and async behaviour. If an endpoint returns 202 and processes asynchronously, document the polling endpoint and the expected lifecycle. If rate limits differ per operation, surface them. The spec is the place — not a separate page on your docs site that downstream tools never read.

The principle: anything a consumer needs to know to call the endpoint correctly belongs in the spec. "It's in the docs" is not a contract.

Write descriptions like they'll be read by a model

Descriptions in OpenAPI used to be a place for marketing copy. "Our powerful invoices endpoint lets you manage billing across your organisation." That sentence is now read by an agent deciding whether to call the endpoint.

What works: state what the operation does in one sentence. State what it returns. State any preconditions. State any side effects. Avoid adjectives. Avoid cross-references to documentation the model can't see.

Bad: Creates a new invoice. See our docs for more.

Good: Creates a draft invoice for the specified customer. Returns the created invoice with status='draft'. The invoice must be finalized via POST /invoices/{id}/finalize before it is sent to the customer.

The second version tells the agent what the operation does, what state the result is in, and what the next step is. That's the level of specificity that translates into correct tool calls.

Keep the spec honest as the API evolves

A spec that was correct at v1.0 and slowly drifted by v1.7 is worse than no spec. Downstream consumers — SDK generators, agent tool definitions, documentation sites — silently produce wrong output. The drift is invisible until something breaks.

Three practices keep the spec honest.

Generate or validate the spec from code

The most reliable specs come from one of two patterns. Spec-first: the OpenAPI document is the source of truth, and your handlers (and generated server stubs, via tools like oapi-codegen) are validated against it at request time, with linting from something like Spectral. Code-first: the spec is generated from typed handlers (FastAPI, NestJS with OpenAPI decorators, Go with huma).

The failure mode to avoid: a hand-maintained spec sitting in a separate repo from the code, updated when someone remembers. That spec will be wrong within a quarter.

Lint the spec in CI

Spectral or Redocly's linter, configured with your organisation's rules, runs on every PR. Rules to enforce: every operation has an operationId, every parameter has a description, every response has a schema, every enum value is documented, no schema duplication. The exact ruleset is less important than the fact that it runs automatically and blocks merge on violations.

Diff the spec against the previous version on every change

oasdiff compares two OpenAPI specs and reports what changed, classified by whether the change is breaking. Run it in CI against the spec from main. Surface breaking changes on the PR. Decide explicitly — not by accident — whether a change is acceptable. We've written about this loop in detail in the breaking-change detection piece linked earlier; the short version is that it turns spec drift from an invisible problem into a visible decision.

What an OpenAPI spec still can't do for agents

A well-written OpenAPI spec is necessary infrastructure for agent access. It isn't sufficient.

Four gaps the spec doesn't close.

Coverage. The spec describes the endpoints that exist. Most established SaaS products built for the UI first; the UI exposes capabilities the API doesn't. Your spec can be perfect and still describe a surface that covers a small fraction of what the product can actually do. We wrote about this in Your APIs expose 2% of what your product can do. Fixing the spec doesn't expand the surface; it makes the existing surface usable.

Composition. Agents rarely call one endpoint. They chain three or four. The spec describes each endpoint in isolation. It doesn't describe useful workflows — "create an invoice, add line items, finalize" — that map to actual user intent. Tool composition is a layer above the spec, and it's where a lot of agent-readiness work actually happens.

Auth at runtime. The spec describes which security schemes exist. It doesn't execute them. An agent calling your API on behalf of a specific user needs OAuth flows, scope checks, token refresh, and an audit trail tied to that user — not a service account. We covered the practical setup in OAuth for AI agents.

Behaviour under failure. The spec lists 4xx and 5xx responses. It doesn't tell the agent which errors are retryable, which require user intervention, which indicate the call should never have been made in the first place. That belongs in a tool runtime layer, not in the OpenAPI document.

The spec is the floor. It's the contract between the API and everything that consumes it. Getting it right is non-negotiable. Treating it as the whole agent-readiness story is where teams end up surprised six months later.

What does an OpenAPI spec look like when the consumer is no longer human?

The honest answer is: more disciplined than most teams currently write. Stable operation IDs. Precise types. Honest required flags. Documented errors. Behavioural descriptions that don't outsource themselves to a separate docs site. Generated or validated from code. Linted in CI. Diffed on every change.

That discipline is not new. It's been good practice for years. What's new is that the cost of skipping it has changed. A vague spec used to mean a worse developer experience. Now it means SDK generators produce broken types, agent tool definitions miss enums, and breaking changes ship without anyone noticing until a customer's integration goes down.

The specs that hold up are the ones treated as production infrastructure, with the same CI discipline as the code they describe. Everything downstream — SDKs, agent tools, docs, contract tests — works only as well as the spec underneath. Start there. Then deal with the harder problems the spec was never going to solve.

Join our weekly newsletter

Stay up to date on the ever changing agentic landscape.

POSTS

Related content

API strategy

Platform integration

API products are not the same as agent-ready products

4 minute read

API strategy

Platform integration

How to detect API breaking changes: a practical guide for agent-era teams

7 minute read

API strategy

Agent infrastructure

API versioning and deprecation when agents are the consumer

9 minute read