API strategy

Agent infrastructure

OpenAPI Generator: a practical guide to generating a client from your spec

OpenAPI Generator walkthrough: install the CLI, validate your spec, generate a TypeScript client, wire auth, and add a CI drift check in seven steps.

6 minute read
Decorative imagery showcasing Pontil's brand

OpenAPI Generator turns an OpenAPI (or Swagger) spec into a working client, server stub, or documentation site. This guide walks you through installing it, generating a TypeScript client from a real spec, wiring auth, regenerating on change, and catching the pitfalls that quietly break generated code in production.

By the end you'll have a typed client you can commit to your repo, a repeatable regeneration command, and a CI check that fails when your spec and code drift. Prerequisites: Node.js LTS (18+ recommended) and Java 11+ (the npm wrapper still shells out to Java), a valid OpenAPI 3.x spec, and about 30 minutes.

Step 1 — Install openapi-generator-cli

The OpenAPI Generator project ships as a Java jar with a Node.js wrapper. The wrapper is easier to pin per project, which matters because generator versions change output between minor releases.

Install it as a dev dependency:

npm install --save-dev @openapitools/openapi-generator-cli

Pin the generator version in openapitools.json at the repo root:

{
 "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
 "spaces": 2,
 "generator-cli": {
   "version": "7.10.0"
 }
}

Check it works:

npx openapi-generator-cli version

Expected output: 7.10.0. If you see a different version, the config file didn't load — check you ran the command from the repo root.

Step 2 — Validate the spec before you generate

Generating from a broken spec produces broken code, and the errors are usually noisier than the underlying problem. Validate first.

Run the built-in validator:

npx openapi-generator-cli validate -i ./spec/openapi.yaml

A clean spec returns No validation issues detected. If you get warnings about missing operationIds, fix them now — every generator uses operationId as the method name, and auto-generated fallbacks like postApiV1UsersUserIdEmails will hurt every caller of your client forever.

For stricter linting, run Spectral alongside the built-in validator. It catches things the OpenAPI Generator will silently accept — undocumented error responses, missing examples, inconsistent naming — that make the generated client harder to use.

Step 3 — Pick the right generator

OpenAPI Generator supports over 50 output targets. List them:

npx openapi-generator-cli list

The choice matters more than it looks. For TypeScript alone there are ten-plus client generators (typescript-fetch, typescript-axios, typescript-node, typescript-angular, typescript-nestjs, typescript-rxjs, and others — see the generators list for the current set), and they produce meaningfully different code. Pick based on the runtime, not habit:
‍

typescript-fetch
typescript-axios
typescript-node

Runtime

Browsers, modern Node

Anywhere with axios

Node.js only

Dependencies

None (uses fetch)

axios

request (deprecated)

Bundle impact

Smallest

Medium

N/A (server-side)

Best for

Frontend, Edge, Workers

Universal clients

Legacy Node services


For most greenfield work in 2026, typescript-fetch is the right default. It has no runtime dependencies and works in browsers, Node 18+, Cloudflare Workers, and Deno.

Step 4 — Generate the client

Run the generator with your chosen target:

npx openapi-generator-cli generate \
 -i ./spec/openapi.yaml \
 -g typescript-fetch \
 -o ./src/generated/client \
 --additional-properties=supportsES6=true,withInterfaces=true,typescriptThreePlus=true

Expected output ends with a summary of files written. Your src/generated/client directory now contains:

  • apis/ — one file per OpenAPI tag, each with typed methods
  • models/ — TypeScript interfaces for every schema in components/schemas
  • runtime.ts — the fetch wrapper, auth handling, and configuration
  • index.ts — barrel exports

Import and use it:

import { Configuration, UsersApi } from './generated/client';

const api = new UsersApi(new Configuration({
 basePath: 'https://api.example.com',
 accessToken: process.env.API_TOKEN,
}));

const user = await api.getUser({ userId: '123' });

Step 5 — Wire authentication properly

The generated Configuration object handles the auth schemes declared in your spec's securitySchemes. It does not handle token refresh, retries, or the OAuth 2.1 flow itself — those are your job.

For a bearer token that rotates, pass a function instead of a string:

const api = new UsersApi(new Configuration({
 basePath: 'https://api.example.com',
 accessToken: async () => {
   const token = await tokenStore.get();
   if (token.expiresAt < Date.now() + 60_000) {
     return tokenStore.refresh();
   }
   return token.value;
 },
}));

The function runs on every request, so keep it cheap. Cache the token, refresh in the background, and never do a synchronous network call inside the accessor.

If your API uses OAuth 2.1 (currently an IETF draft that consolidates OAuth 2.0 best practices) with delegated user identity — which is the pattern that survives security review when agents are the caller — the generator gets you the request shape, not the token lifecycle. That's a separate build.

Step 6 — Regenerate on spec change without losing changes

Generated code is not source code. Treat the output directory as a build artifact and never hand-edit it. If you patch a bug in apis/UsersApi.ts, the next regeneration wipes it.

Add a regeneration script to package.json:

{
 "scripts": {
   "generate:client": "openapi-generator-cli generate -i ./spec/openapi.yaml -g typescript-fetch -o ./src/generated/client --additional-properties=supportsES6=true,withInterfaces=true",
   "generate:clean": "rm -rf ./src/generated/client && npm run generate:client"
 }
}

Add a .openapi-generator-ignore file in the output directory for the rare cases where you genuinely need to override a generated file. Same syntax as .gitignore.

Commit the generated code. Reviewers should see when the client surface changes — a diff on apis/UsersApi.ts in a pull request is exactly the signal you want when the underlying API changed.

Step 7 — Add a drift check in CI

The failure mode that catches most teams: the spec ships, the code doesn't regenerate, and the client silently lags the API for weeks. Add a CI job that fails when the two disagree.

# .github/workflows/openapi-drift.yml
name: OpenAPI drift check
on: [pull_request]
jobs:
 drift:
   runs-on: ubuntu-latest
   steps:
     - uses: actions/checkout@v4
     - uses: actions/setup-node@v4
       with:
         node-version: '20'
     - run: npm ci
     - run: npm run generate:clean
     - run: git diff --exit-code src/generated

If regenerating produces a diff, the job fails. The author has to either commit the regeneration or explain why the spec changed without the client following.

This is the same pattern we cover in more depth in the guide on generating an OpenAPI spec from an existing codebase — contract and code drift is easier to prevent than to hunt down.

Common pitfalls

Missing operationIds produce unusable method names. OpenAPI Generator falls back to path-based names when operationId is absent. postApiV2OrganizationsOrgIdMembersMemberIdRolesRoleId() is a real thing generators produce. Fix operationIds in the spec before generating anything you'll import.

Additional properties matter more than the generator name. typescript-fetch alone produces very different output depending on withInterfaces, typescriptThreePlus, useSingleRequestParameter, and modelPropertyNaming. Read the generator's docs page and pick deliberately. See npx openapi-generator-cli config-help -g typescript-fetch for the full list.

Generated enums are strict. If your spec declares status: enum: [active, pending, cancelled] and the API starts returning archived, the generated client throws at deserialization. If your API adds enum values on a rolling basis, either declare the field as a plain string or use enumPropertyNaming=original and handle the unknown value in your code.

The generated code doesn't handle retries, backoff, or circuit breakers. It's a client, not a resilience layer. Wrap it in your own retry logic — the circuit breaker pattern guide covers the shape that holds under agent load.

Version pinning is not optional. OpenAPI Generator's output changes between 7.x releases. Pin the version in openapitools.json, commit it, and upgrade deliberately — regenerate, review the diff, run your tests. Don't let a floating version silently reshape your client on a Tuesday.

The generator does not know your API's real behaviour. It knows what the spec says. If the spec is wrong — and specs drift from behaviour more than most teams realise — the generated client will confidently call endpoints that don't exist in the shape it thinks they do. Contract tests against the running API are the only way to catch this.

Join our weekly newsletter

Stay up to date on the ever changing agentic landscape.

POSTS

Related content

API strategy

Platform integration

How to generate an OpenAPI spec from an existing codebase

7 minute read

API strategy

Agent infrastructure

OpenAPI spec drift: why your contract lies before your agents break

9 minute read

API strategy

Platform integration

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

9 minute read