Writing OpenAPI Specs With LLMs in October 2026: Lint Every Draft, Test Against the Real API, and Diff for Breaking Changes

Large language models are quick at producing OpenAPI specifications. Paste in a few route handlers or a rough description of an endpoint, and you get back hundreds of lines of YAML that look complete. The trouble is that an OpenAPI file is a contract: client SDKs, mock servers, API gateways, and documentation sites all trust it. A spec that looks right but describes a field that doesn't exist, or quietly mixes OpenAPI 3.0 and 3.1 syntax, causes problems far from where the mistake was made.

Artificial Neural Network with Chip
Image: mikemacmarketing / photo on flickr via Wikimedia Commons (CC BY 2.0)
Neural network   Midjourney and Grok
Image: Midjourney; prompt suggested by Grok via Wikimedia Commons (Public domain)

This guide covers a practical workflow for using LLMs to draft and maintain OpenAPI specs without letting errors slip into the contract: give the model the right inputs, lint every draft, check examples against schemas, test the spec against the running API, and diff every change for breaking changes before it merges.

Where LLM-Written Specs Usually Go Wrong

The failures are predictable, which makes them easy to check for once you know them:

  • Version mixing. OpenAPI 3.0 and 3.1 handle nullability differently. In 3.0 you write nullable: true. In 3.1, which aligns with JSON Schema 2020-12, nullable is gone and you write type: [string, "null"]. Models trained on years of both often blend them in one file.
  • Invented fields and endpoints. If the model only sees part of your code, it fills gaps with plausible names like created_at or /users/{id}/settings whether or not they exist.
  • Missing error responses. Drafts tend to document the happy path (200) and skip the 400, 401, 404, and 422 responses your API actually returns.
  • Examples that don't match schemas. An example with a string ID while the schema says integer, or an enum value that isn't in the enum list.
  • Loose schemas. Everything typed as string, no required lists, no format, and no limits, which makes the spec nearly useless for validation.
  • Inconsistent naming. userId in one schema and user_id in another, or operationId values that collide.

Step 1: Give the Model Real Inputs, Not a Description

The best way to stop invented fields is to give the model the source of truth. Depending on your stack, that means the route definitions, request validation code, serializers or response DTOs, and the database model for the resources involved. If your framework can already generate a partial spec (FastAPI, NestJS, and many others can), start from that output and ask the model to fill in descriptions, examples, and error responses rather than writing from scratch.

State the constraints explicitly in the prompt:

Write an OpenAPI 3.1.0 spec for the endpoints below.
- Use only fields that appear in the provided code. If a field is unclear, add a TODO comment instead of guessing.
- Use JSON Schema 2020-12 nullability (type: [x, "null"]), never nullable: true.
- Put shared objects under components/schemas and reference them with $ref.
- Document every error status the handler can return, using the shared Error schema.
- Give every operation a unique camelCase operationId.

Asking for TODO markers instead of guesses is important. A visible TODO is easy to fix in review; a confident invented field is not.

Step 2: Lint Every Draft

Never review a generated spec by eye first. Run a linter, fix what it reports, and only then read it. Two widely used open-source options are Spectral and Redocly CLI:

# Spectral with its built-in OpenAPI ruleset
npx @stoplight/spectral-cli lint openapi.yaml

# Redocly CLI
npx @redocly/cli lint openapi.yaml

For Spectral, a minimal ruleset file named .spectral.yaml looks like this:

extends: ["spectral:oas"]
rules:
  operation-operationId: error
  operation-description: warn
  oas3-valid-media-example: error

The built-in rules catch structural problems such as unresolved $ref targets, duplicate operation IDs, and invalid examples. Add your own rules for house conventions, such as snake_case property names or a required Error response on every operation. Once those rules exist, feed the lint output back to the model and ask it to fix only the reported problems. That loop is much more reliable than asking it to "double-check the spec."

Step 3: Make Examples Prove the Schemas

Examples are where LLM drafts most often contradict themselves, and they matter because documentation sites and mock servers show them to developers. Treat each example as a test case for its schema.

  • Turn on the linter rule that validates examples against schemas (in Spectral's OpenAPI ruleset, the oas3-valid-media-example and oas3-valid-schema-example rules do this).
  • Replace invented example data with sanitized real responses captured from a staging environment. Real payloads expose fields the model missed.
  • Include at least one error example per operation so client developers can see the actual error shape.

Step 4: Test the Spec Against the Running API

A spec can be valid and still wrong about your API. Close that gap by testing it against a real server.

Property-based contract testing. Schemathesis reads your spec, generates requests from the schemas, and checks that responses match what the spec says. Run it against a local or staging instance, never production:

schemathesis run openapi.yaml --url http://localhost:8000

Failures usually fall into two groups: the spec is wrong (an undocumented status code, a field marked required that the server omits) or the server is wrong (a 500 on input the spec allows). Both are worth knowing.

Response validation in tests. If you already have integration tests, add a step that validates each real response against the spec using an OpenAPI validation library for your language. This catches drift every time the test suite runs.

Mock servers for client teams. Tools like Prism can serve a mock API directly from the spec (prism mock openapi.yaml). If front-end developers build against the mock and then hit differences in the real API, that is a spec bug you found early.

Step 5: Diff Every Change for Breaking Changes

The riskiest moment is not the first draft; it is when someone asks a model to "update the spec for the new endpoint" and it also renames a field, tightens an enum, or makes an optional parameter required. Run a breaking-change check in CI against the spec on your main branch:

oasdiff breaking main-openapi.yaml openapi.yaml

oasdiff reports changes such as removed endpoints, removed response properties, new required request fields, and narrowed types. Decide on a policy: either block the merge on breaking changes or require an explicit version bump and a changelog entry. Either way, nobody should discover a breaking change from a failing client in production.

Step 6: Review What Tools Can't Check

Linters and contract tests don't know your business rules. A human review should focus on:

  • Security schemes. Is every operation covered by the right security requirement? Did the model mark an admin endpoint as public?
  • Sensitive fields. Make sure internal IDs, password hashes, or tokens don't appear in response schemas just because they exist on the database model.
  • Pagination and limits. Check that limit has a maximum, that cursor parameters are documented, and that large list endpoints are not described as returning everything.
  • Descriptions. Descriptions should explain behavior ("returns 404 if the project is archived"), not restate the field name.

A Workflow That Holds Up

  1. Generate a partial spec from your framework if it can, or collect the route and model code.
  2. Prompt the model with that code, the target OpenAPI version, and explicit rules, including "TODO instead of guessing."
  3. Lint with Spectral or Redocly and loop the errors back to the model until the output is clean.
  4. Replace examples with sanitized real payloads and validate them against the schemas.
  5. Run contract tests against a staging or local server.
  6. Run a breaking-change diff against the main branch in CI.
  7. Do a human review of security, sensitive fields, and limits before merging.

LLMs are genuinely useful here. They save hours of typing descriptions, building component schemas, and writing examples. But the spec is a contract that other code relies on, so the model should only produce the draft. Linters, contract tests, and diffs decide whether it gets merged.

Comments

Popular posts from this blog

Grok Bot - a step closer to AGI

Structured Outputs and Constrained Decoding for Reliable LLM APIs in Production (September 2026)

Tencent Hy4 Preview: Open 770B MoE Built for Real Work