All guides
Integration

Generate TypeScript Types from OpenAPI in Seconds

Turn any OpenAPI or Swagger spec into clean TypeScript types via Outworx Docs' MCP server — no CLI, no codegen configs. Generate live, cached, and current.

Abdalla Emad· Founder, Outworx DocsApril 24, 20266 min read

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

ApproachSetup timeStays in sync?Handles incomplete specs?Where types live
openapi-typescript CLI5 minManual re-runNo — unknown for missing schemasChecked into git
orval codegen15 minCI-drivenNoChecked into git, includes hooks
Outworx MCP generate_types30 secLive, on-demandYes — infers from real responsesEither: paste into git OR consume via MCP
Hybrid (MCP + checked-in cache)30 secRe-prompted on driftYesBoth

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.ts

The 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-types

Where 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 orval

Drop 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 orval

You 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 listPosts endpoint."

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, OpNameError aliases
  • 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:

  1. Use the MCP generate_types tool to draft types for the endpoints you actually use (not the whole spec)
  2. Paste into src/types/api.ts (or a dedicated @company/api-types package)
  3. When the AI hits your API via execute_endpoint later and detects drift, it surfaces a "your cached types are stale, regenerate?" prompt
  4. 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

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.