If you've ever integrated against a REST API from a TypeScript codebase, you know the drill. You read the OpenAPI spec, hand-write interfaces for the request body and response, miss a nullable field, ship a bug, and find it at 2am when production returns a shape you didn't expect. The next week the API adds a field, nobody remembers to update your types, and the whole cycle repeats.
There's a better way. Your OpenAPI spec already describes every request, response, and error shape. The job of turning that spec into TypeScript types should be automatic — and ideally it should stay accurate as the API drifts.
This guide covers three approaches end-to-end: the classic CLI codegen route (good for monorepos that want types checked into git), the modern MCP-powered approach (best ergonomics for AI-assisted workflows), and a hybrid that gets you the best of both. We'll show working code for each, the tradeoffs, the failure modes, and a short FAQ at the end for the questions that always come up.
Approaches at a glance
| Approach | Setup time | Stays in sync? | Handles incomplete specs? | Where types live |
|---|---|---|---|---|
openapi-typescript CLI | 5 min | Manual re-run | No — unknown for missing schemas | Checked into git |
orval codegen | 15 min | CI-driven | No | Checked into git, includes hooks |
Outworx MCP generate_types | 30 sec | Live, on-demand | Yes — infers from real responses | Either: paste into git OR consume via MCP |
| Hybrid (MCP + checked-in cache) | 30 sec | Re-prompted on drift | Yes | Both |
If you have a single API and a small monorepo, the CLI route is simplest. If you're using Cursor / Claude Desktop / Cline daily, MCP wins on ergonomics. If you ship to production and want auditable types in git, hybrid is the answer.
Approach 1 — openapi-typescript CLI
The most-installed option (openapi-typescript has ~3M weekly downloads). It reads your spec and writes a single .d.ts file of interfaces.
# Install once
npm install -D openapi-typescript
# Generate types from a spec URL
npx openapi-typescript https://api.example.com/openapi.json \
--output src/types/api.d.ts
# Or from a local file
npx openapi-typescript ./openapi.yaml --output src/types/api.d.tsThe output looks like this:
export interface paths {
"/users": {
get: operations["listUsers"];
post: operations["createUser"];
};
"/users/{id}": {
get: operations["getUser"];
};
}
export interface operations {
listUsers: {
parameters: { query?: { page?: number; limit?: number } };
responses: {
200: { content: { "application/json": components["schemas"]["UserList"] } };
401: { content: { "application/json": components["schemas"]["Error"] } };
};
};
// ...
}It's verbose by design — you address types via the paths and operations indices. The openapi-fetch companion package makes this ergonomic:
import createClient from "openapi-fetch";
import type { paths } from "@/types/api";
const client = createClient<paths>({ baseUrl: "https://api.example.com" });
const { data, error } = await client.GET("/users", {
params: { query: { page: 1, limit: 20 } },
});
// data is fully typed; error is the typed 4xx/5xx union.Wire it into CI so it runs on every spec change:
# .github/workflows/regenerate-types.yml
name: Regenerate API types
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
jobs:
regenerate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- run: npx openapi-typescript $SPEC_URL -o src/types/api.d.ts
- uses: peter-evans/create-pull-request@v6
with:
title: "chore: regenerate API types"
branch: chore/regenerate-typesWhere this approach falls down: if your spec is incomplete — and most are, because most teams generate the spec from server code that doesn't fully describe response shapes — you get unknown everywhere a schema is missing. You're back to hand-typing.
Approach 2 — orval for hooks + types
orval builds on top of openapi-typescript and emits a fully-typed client per endpoint, with React Query / SWR / Vue Query / Angular hooks. It's the heavier, more-opinionated option.
npm install -D orvalDrop a config file:
// orval.config.ts
import { defineConfig } from "orval";
export default defineConfig({
api: {
input: "https://api.example.com/openapi.json",
output: {
target: "./src/api/generated.ts",
client: "react-query",
mode: "split",
mock: true,
},
},
});npx orvalYou get a per-endpoint hook plus its types in one bundle:
import { useListUsers } from "@/api/generated";
function UserList() {
const { data, isLoading } = useListUsers({ page: 1, limit: 20 });
// data is typed as ListUsersResponse | undefined
}The cost is heavier output, more opinion, and a slow regeneration step. For codebases that already use React Query, the ergonomic win is real. For everything else, the CLI is leaner.
Approach 3 — MCP generate_types (the modern way)
Outworx Docs ships a per-project Model Context Protocol server. Your API already has docs hosted at yourslug.docs.outworx.io; the MCP endpoint at docs.outworx.io/api/mcp/yourslug exposes 12 tools that AI assistants like Claude Desktop, Cursor, Cline, and Continue can call — including generate_types.
From inside Cursor (or Claude Desktop, or wherever), you literally ask:
"Generate TypeScript types for the
listPostsendpoint."
The assistant calls generate_types with scope="endpoint", gets back a self-contained TypeScript module, and drops it into your codebase. Under the hood, Outworx:
- Parses your hosted spec into a normalized internal model
- Renders every named schema as an
export interface - Emits per-endpoint
OpNameRequest,OpNameResponse,OpNameErroraliases - Caches the result so the next caller gets it instantly
Here's what you get back for one endpoint:
// Types for listPosts — GET /api/feed/posts
// Generated by Outworx MCP at 2026-05-03T08:14:22Z
export interface ListPostsQueryParams {
/** Page number (1-indexed) */
page?: number;
/** Results per page (max 100) */
limit?: number;
/** Filter by author username */
author?: string;
}
/** 200 — Page of posts */
export type ListPostsResponse = {
items: Post[];
total: number;
page: number;
has_more: boolean;
};
/** 401 — Unauthorized */
export type ListPostsError401 = {
error: string;
code: string;
};
export interface Post {
id: string;
author_id: string;
content: string;
created_at: string;
/** community-inferred — see "Self-healing types" below */
reactions?: Reaction[];
}Notice the community-inferred comment on reactions. That's the killer move.
Self-healing types
When an MCP client calls execute_endpoint with real credentials, Outworx captures the response body and merges it into an inferred schema stored per (version, endpoint, status). Over multiple samples from multiple users, the stored schema converges on the real contract — including fields the OpenAPI spec never documented.
The next generate_types call uses the inferred schema as a fallback, so generated TypeScript reflects actual API behavior even when the spec is incomplete. And because every live call re-validates, drift is caught immediately and the cached types regenerate cleanly.
This is particularly powerful for:
- APIs specced after the fact (FastAPI, Django REST Framework, Express)
- Community-driven APIs where the spec trails the implementation
- Internal APIs where the spec is hand-maintained and always slightly out of date
The types improve as people use them. No CLI codegen tool can do this — they only see the spec, not the wire.
Approach 4 — Hybrid (MCP + checked-in cache)
For production TypeScript monorepos you still want types checked into git so offline builds and deterministic CI work. The MCP flow dovetails with that pattern:
- Use the MCP
generate_typestool to draft types for the endpoints you actually use (not the whole spec) - Paste into
src/types/api.ts(or a dedicated@company/api-typespackage) - When the AI hits your API via
execute_endpointlater and detects drift, it surfaces a "your cached types are stale, regenerate?" prompt - Regenerate, commit, ship
You get the ergonomic benefit of on-demand generation plus the audit-friendly behavior of checked-in types. The package boundary makes the regeneration explicit — you'll see the diff in code review.
Common errors and how to fix them
Cannot find module '@/types/api.d.ts' after running codegen. Check your tsconfig.json paths mapping. The CLI tools default to writing under src/types/, but if your alias @/* points to ./src/* you may need to refresh the TypeScript server (Cmd+Shift+P → Restart TS server in VS Code / Cursor).
Types are all unknown for response bodies. Your spec has application/json declared but no schema — the API ships an unstructured response. Two fixes: (a) update the source code to declare a response model (FastAPI response_model=, NestJS @ApiResponse({ type: ... }), etc.), or (b) use Outworx MCP, which infers the schema from real traffic and patches the gap.
429 Too Many Requests from the spec URL during CI. The CLI is fetching your spec on every CI run. Cache it: download the spec into the repo as openapi.lock.json, run codegen against the local file, and only refresh the lockfile on a scheduled job.
Generated types break after a backend deploy. The spec changed and your client wasn't regenerated. Fix in the long run by running codegen on a daily cron and opening a PR. In the short run, regenerate locally and commit.
Frequently asked questions
Does this work for Swagger 2.0? Yes. Both openapi-typescript and the Outworx MCP server support OpenAPI 3.x and Swagger 2.0. Swagger 2.0 produces slightly less precise types because it lacks oneOf / anyOf, but it works.
Does this work for GraphQL? GraphQL has its own ecosystem (graphql-codegen, gql.tada, Apollo, Relay). For GraphQL specifically, use graphql-codegen — it's the equivalent of openapi-typescript for GraphQL. Outworx hosts GraphQL docs and a GraphQL mock server, but type generation is downstream of the GraphQL ecosystem.
Can I generate types for a private API? Yes, with both approaches. For the CLI, pass an authenticated spec URL (Authorization: Bearer ... via --header). For Outworx MCP, the execute_endpoint tool prompts the user for their auth, then the inferred schema improves as they use real credentials.
What happens if my API uses oneOf / anyOf / allOf? All three CLI approaches support them, with caveats. openapi-typescript produces a discriminated union you can switch on; orval does too. Outworx MCP resolves them at generation time and emits a clean union type. Refs ($ref) are followed and inlined.
How do I version my types so old clients keep working? Tag the spec at each release (e.g., v1.openapi.json, v2.openapi.json) and generate per-version type packages. Outworx supports per-version mock URLs and per-version MCP scopes natively, so an MCP client targeting v1 gets v1 types.
Do generated types include doc comments? Yes, all three approaches preserve the OpenAPI description field as JSDoc. Outworx adds an extra /** community-inferred */ annotation on fields it inferred from real traffic — useful in code review.
Can I commit the generated file to git but ignore the diff in PRs? Add src/types/api.d.ts linguist-generated=true to .gitattributes. GitHub will collapse the diff by default in PRs but still track the file.
What's the tradeoff vs hand-writing types? Hand-writing is faster for one-off scripts. Codegen wins the moment you need to call more than ~5 endpoints, or any time the API changes. The Outworx MCP flow is faster than hand-writing even for one-off scripts because the AI does the typing for you.
Try it
If you already host your API docs on Outworx, open the MCP tab on your project, copy the Cursor or Claude Desktop snippet, paste it into your MCP config, and ask your AI assistant to generate types for any endpoint. The whole loop takes about 30 seconds — including the moment when the assistant first realizes it can call your live API.
If you don't host your docs with us yet, that's the next 30 seconds. Upload your OpenAPI spec, get a hosted URL, and the MCP endpoint comes free on Pro and Business plans. The mock server, AI chat, webhook playground, and all the other pieces of the API toolchain are right there next door if you need them.
Related guides
Outworx REST API — Manage Projects from CI
Drive every Outworx Docs action from a script: create projects, push spec versions, promote a default. Personal access tokens, copy-paste curl, and the full OpenAPI spec.
Outworx CLI — push specs from anywhere
Publish OpenAPI / Swagger / GraphQL specs to Outworx Docs from your laptop or any CI provider. One command, zero install graph, scriptable exit codes.
PR preview deploys — every pull request gets its own docs URL
Push specs from CI with `--preview` and Outworx spins up a throwaway version with a banner showing PR / branch / commit. 14-day rolling TTL, hidden from public version listings, three lines of GitHub Action.
Ship your API docs in under a minute.
Upload your OpenAPI, Swagger, or GraphQL spec and get beautiful hosted docs with AI chat and a per-project MCP server — free forever for 2 projects.