Skip to content
0degrees.ai
Tooling

Using AI to Generate and Maintain OpenAPI Specs

How to use AI to extract OpenAPI specs from existing routes, generate type-safe clients, and keep spec and implementation in sync — without manual YAML.

0degrees Team 7 min read

Writing OpenAPI specs by hand is tedious and error-prone. Most teams either skip the spec entirely, generate it from decorators that clutter production code, or maintain it separately until it drifts from the real implementation. AI coding assistants change the calculus here — not by writing YAML for you, but by acting as a bidirectional bridge between your code and your contract.

This post covers three concrete workflows: extracting a spec from existing routes, generating type-safe clients from a spec, and using AI to detect when the two have drifted apart.

Extracting a spec from existing route handlers

If your API exists and your spec doesn’t, the first task is extraction. This is where AI excels, because it requires exactly the kind of systematic reading and reformatting that’s tedious for humans but straightforward for a model.

The prompt structure that works:

Read the route handlers in src/routes/users.ts and generate an OpenAPI 3.1
path object for each route. For each path:
- Extract the HTTP method, path template, and any path parameters
- Infer the request body schema from the Zod validator or TypeScript type at
  the top of the handler
- Infer the response schema from the return type annotation or explicit
  res.json() calls
- Annotate each response with the correct HTTP status codes observed in the code

Output valid YAML. Do not invent fields that aren't present in the code.
If a field is ambiguous (e.g. the validator and the return type disagree),
note the discrepancy inline as a comment rather than silently resolving it.

The last instruction is important. When you ask AI to “generate the spec,” it tends to resolve ambiguities quietly by making a plausible choice. Asking it to surface discrepancies instead turns the extraction into an audit — you find out which routes have a mismatch between what they validate and what they return.

A real discrepancy it might flag:

# /users/{id} GET
# NOTE: The route validates that `id` is a UUID string, but the return type
# declares `User & { role: string }` while the DB query only selects columns
# without `role`. Spec generated from the DB query columns — verify intent.
responses:
  '200':
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/User'

That comment is worth more than the YAML around it. It identifies a bug (or a stale type) that a purely mechanical extraction would have silently baked in.

Generating a type-safe client from a spec

Once you have a spec — whether extracted from code or written first — AI can generate the client-side fetch layer from it. The advantage over code-gen tools like openapi-typescript-codegen is that you can shape the output to fit your project’s conventions, handle your auth pattern, and generate exactly the error-handling style your codebase uses.

Start by giving the model a representative sample of your existing fetch code so it can match the style:

Here is how we currently write API calls in this project:

  async function getUser(id: string): Promise<User> {
    const res = await apiFetch(`/users/${id}`);
    if (!res.ok) throw new ApiError(res.status, await res.json());
    return res.json() as Promise<User>;
  }

Using the OpenAPI spec below, generate a complete API client module at
src/lib/api.ts. Each operation becomes a typed function following the
pattern above. Group related operations into objects (users, posts, etc.).
Import types from src/types/api.ts — generate that file too, deriving
TypeScript interfaces from the spec's component schemas.

[paste spec here]

This produces an API client that:

  • Matches your existing error handling
  • Exports TypeScript types that match the spec
  • Groups operations in a way that fits how your UI code already works

The generated src/types/api.ts is what makes this durable. When the spec changes, you re-run the generation and the TypeScript compiler tells you everywhere the old types are used — you get a free diff of what needs updating.

Detecting drift between spec and implementation

The hardest part of maintaining an OpenAPI spec isn’t writing it — it’s keeping it accurate as the implementation changes. AI can help here too, used as a diff reviewer rather than a generator.

Set this up as a periodic check rather than a manual process. Create a prompt you can run any time a route file changes:

Compare the attached OpenAPI spec (openapi.yaml) against the route handlers
in src/routes/. For each route in the spec:

1. Find the matching handler in the source
2. Check that the request body schema matches the Zod validator in the handler
3. Check that each documented response status code is actually reachable in
   the handler
4. Check that any documented path or query parameters are actually read by
   the handler

Report only genuine discrepancies. Ignore stylistic differences and 
TypeScript vs. JSON Schema type name differences (e.g. `string` vs
`{ type: "string" }`). Format the output as a numbered list:
  - Route + method
  - What the spec says
  - What the code does
  - Recommended fix

Run this whenever your route files change — either manually or as a step in CI that posts a comment to the PR. The output will be imperfect (AI is not a parser), but it catches the majority of drift: a validation rule that was tightened in code but not in the spec, a 404 that was added to a handler but never documented, a field that was renamed on the response.

What pushed us to formalize this — 0degrees Team: We had a payment route where the spec documented the success response as { orderId: string } and a developer had renamed the field to { order_id: string } six months earlier when we standardized on snake_case. The spec sat wrong for six months because nobody read it unless they were writing a new client. We caught it when a mobile team filed a bug. An AI drift check at PR time would have flagged it on the day the rename landed.

Keeping the spec as the source of truth

The extraction-first workflow above is useful when you’re adding a spec to an existing API. For new routes, the spec-first workflow is stronger: write the OpenAPI path object, then use AI to scaffold the handler from it.

Here is the OpenAPI spec for the new PATCH /users/{id}/settings route:

[paste the path object]

Generate:
1. The Zod request body validator matching the spec's requestBody schema
2. The route handler at src/routes/users.ts that validates the body,
   calls updateUserSettings() from src/lib/users.ts, and returns the
   documented response shapes
3. The TypeScript response type in src/types/api.ts

The existing GET /users/{id} handler is at line 42 of src/routes/users.ts —
match its structure and error handling style.

This produces code that matches the contract by construction, not by inference. The Zod schema is derived directly from the spec’s JSON Schema, so it’s mechanically consistent. The generated handler documents its own contract in code.

The workflow in practice

These three workflows compose into a practical rhythm:

  1. New API: Write the spec first (OpenAPI YAML or JSON), then scaffold the handler from it. The spec is the source of truth.
  2. Existing API without a spec: Use AI extraction to generate a draft spec. Treat the discrepancy comments as a bug list to address before finalizing the spec.
  3. Ongoing maintenance: Run the drift check whenever route files change. Re-generate the client types whenever the spec changes. Let the TypeScript compiler surface usage sites that need updating.

None of this requires a new dependency or a build-time code-gen pipeline. The spec is a plain YAML file in your repo. The client is a module you regenerated from it. The types are TypeScript interfaces derived from schemas. AI is the glue that keeps them consistent.

What AI doesn’t replace

AI extraction is not a parser. It infers schemas from code it reads, and it will occasionally misread a complex validator or miss a conditional response. Always review the generated spec before committing it — treat it as a starting draft that needs your sign-off, not a mechanical output you can blindly trust.

Similarly, the drift check will produce false positives and miss some real issues. It’s a complement to contract tests, not a replacement. If spec accuracy matters for your team (e.g., you’re publishing the spec for external consumers), the right long-term answer is to generate the spec from decorators or from TypeScript types at build time, with AI helping to write the annotations correctly. See Writing Effective Project Instruction Files for how to encode your API conventions so AI generates consistent annotations across every route.

Used as a practical shortcut rather than a formal guarantee, AI-assisted OpenAPI maintenance eliminates most of the friction that makes teams skip the spec entirely. A draft spec with a few inaccuracies that gets refined is more useful than no spec at all — and more accurate than one written by hand six months ago and never updated since.

[ Related ]

Keep reading