CLI tool
readmeio/api avatar
readmeio/api

readmeio/api: generating a typed TypeScript SDK from an OpenAPI file

🚀 Automatic SDK generation from an OpenAPI definition

697 stars31 forksTypeScriptMIT

At a glance

What is it?
The api library takes an OpenAPI definition and produces an opinionated JavaScript or TypeScript SDK, installed with a single npx command that produces a scoped package you import like any other dependency. It suits a team that publishes an API and wants a client without hand-writing one, and it is a poor fit if you need a generator that supports many schema dialects or many target languages.
Who is it for?
api fits a team publishing an HTTP API whose consumers keep complaining that the client is stale or untyped, since generating a scoped package from the same definition the server publishes removes the drift by construction, and the generated methods return a data property with TypeScript types included.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One command turns a definition into a package you import

The api library is described as magical SDK generation from an OpenAPI definition, and the tooling it offers creates an opinionated SDK for TypeScript or JavaScript, with TypeScript types included. The install is a single npx invocation against a URL:

bash
$ npx api install https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json

The result is a package you consume like any other dependency, and the readme shows the CommonJS form:

js
const petstore = require('@api/petstore');

petstore.listPets().then(({ data }) => {
  console.log(`My pets name is ${data[0].name}!`);
});

and the ESM form, which the project states is supported as well:

js
import petstore from '@api/petstore';

petstore.listPets().then(({ data }) => {
  console.log(`My pets name is ${data[0].name}!`);
});

Two details are visible in that snippet. The generated package is scoped to @api with the project name appended, so the OpenAPI title becomes the import name, and the calls are methods on the default export rather than free functions, which means the client holds its own configuration.

The shape of the return value matters more than it looks. Each call resolves to an object with a data property, which is where the response body sits. That is a deliberate choice by the generator rather than an accident of the example, because it leaves room for the other parts of a response, and a hand-written client that returns the body directly cannot be extended later without breaking callers.

The alternative to all of this is writing the client by hand, which is the option worth naming explicitly. A hand-written client gives exact control over the request shape, over error types, over retries and over how authentication is attached, and it is often faster for a small API with three endpoints. The generated client wins when the surface is wide, when the definition already exists, and when the cost of the client drifting out of date is high.

The documentation site is api.readme.dev, and the readme links to separate pages for installation, usage, authentication, parameters and payloads, making requests, server configurations, a page explaining how it works, and a FAQ.

The documentation is organised around the decisions a client author has to make

The structure of the documentation is a good hint about what the generator actually controls, and it is worth reading before you commit to it.

There is a page on authentication, which means the generator has an opinion about where credentials go rather than leaving it to the caller. There is a page on parameters and payloads, which means path, query and body parameters are modelled explicitly. There is a page on making requests, which is where the request configuration lives. And there is a page on server configurations, which is the mechanism for pointing the same client at a staging host or a local mock rather than at production.

That last one deserves emphasis. A generated client that hardcodes a base URL is much less useful than one that does not, and a client that cannot be pointed at a mock server is hard to test against. The presence of a dedicated server configuration page says the generator treats the base URL as data.

The how it works page is the one to read if you want to know what the generator does with your definition rather than how to call the result. The FAQ is where the sharp edges usually live, and the readme points at it as a first-class page rather than an afterthought.

The repository layout backs the picture of a serious tool rather than a weekend script. There is a packages/ directory under npm workspaces, a docs/ directory, a bin/ directory, a CHANGELOG.md, a .changeset/ directory, a knip.ts file for unused-export detection, and lint and format configuration built on oxlint and oxfmt, with oxfmt.config.ts and oxlint.config.ts at the root. There is also a LICENSES/ directory, which is where a project records the licences of the code it vendors.

A turbo monorepo with changesets, vitest and a typed package check

The root package.json is named api-monorepo, is private, declares itself an ES module, and uses npm workspaces over ./packages/*. The package manager is pinned to npm at 10.9.4 and the engines field requires node 20.10.0 or newer.

The build and release tooling is where the details are. Turbo runs the build across packages, and the build script is a plain turbo run build. Releases go through changesets: there is a changeset script, a version-packages script that runs changeset version, and a release script that runs changeset publish, with a prerelease script that runs the build first.

That is a modern, careful setup. Changesets mean each package declares its own version bump in a markdown file, and a pull request that changes one package does not bump the others. The .changeset/ directory in the tree is where those files live.

The test story is vitest with coverage, and a pretest script that runs the build first, so the tests always run against built output rather than against source. There is a separate smoke test script scoped to packages/api, which is the package that matters most. There is also an attw script, which runs the arethetypeswrong CLI across packages with a concurrency of one, and that tool exists to answer a question most people never ask: does this package work correctly under every module resolution mode, with correct types, in both ESM and CommonJS projects. For a package whose whole job is to be consumed by other people's builds, that is exactly the right check to automate, and running it with concurrency one keeps the output readable.

The lint script is a chain: knip, then a type lint across workspaces, then oxlint, then an oxfmt check. Knip is there to find unused files, dependencies and exports, which in a monorepo with many packages is the usual source of dead weight. The format script applies oxlint fixes and writes with oxfmt.

Where api will not fit your team

The first limitation is the output target. The readme states the codegen offering creates an SDK for TypeScript or JS. The repository topics are api, openapi, sdk and swagger, and there is no mention of a Python, Go, Java or Ruby generator, so if your API consumers are not on the JavaScript side of the fence, this library does not serve them. A generator that covered only two of the languages your consumers write would push the work back onto you.

The second is the opinionated part, and the readme uses the word deliberately. An opinionated generated client is faster to use because you do not choose between a dozen request options, and it is harder to bend when your API has a quirk. A generator that produces a single method per operation with a data property on the result is a design decision, and if your API needs a streaming response, a long-poll, a multipart upload with progress, or a non-JSON content type, the generated shape may not express it at all. Read the how it works page and the FAQ before you assume it does.

The third is the coupling to a hosted platform. The package is named api and the project lives under the readmeio organisation, and the generated packages are scoped to @api. The documentation lives on api.readme.dev, which is the project's own site. A team with a policy about depending on a commercial vendor's hosted services, or about the long-term availability of a public scope, should check where the generated package is actually published and what the fallback is, because the readme does not say.

The fourth is the release state. The published tags are v7.0.0-beta.13 from 2025-04-04, v6.1.3 from 2025-02-24 and v6.1.2 from 2024-07-03, so the newest published artefact is a beta of a major version. The last push to the repository was on 2026-10-13, which means the working tree has moved on considerably from the last published tag, and a v7 release may or may not be imminent. Pinning to a beta is a decision you should make deliberately.

The fifth is that generation does not remove the need for judgement. A generated client knows the paths and the schema; it does not know that your API has a rate limit, that a particular endpoint returns a 200 with an error body, or that one of its operations takes thirty seconds and needs a longer timeout.

Editorial conclusion

api fits a team publishing an HTTP API whose consumers keep complaining that the client is stale or untyped, since generating a scoped package from the same definition the server publishes removes the drift by construction, and the generated methods return a data property with TypeScript types included. It is the wrong choice if you need output for languages other than JavaScript and TypeScript, if your OpenAPI file uses schema features the generator does not support, or if you want a client that mirrors your API's wire format exactly rather than an opinionated one. Before adopting it, run npx api install against your own OpenAPI file, read the generated package before publishing it, and check the authentication and server-configuration options in the documentation, since the last push was on 2026-10-13 and the newest published tag is v7.0.0-beta.13 from 2025-04-04.

Frequently asked questions

How do I generate an SDK with the api library?

Run npx api install with the URL of your OpenAPI definition, for example npx api install https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json. The generated package is then imported as a scoped dependency.

Can I use the generated SDK with ESM as well as CommonJS?

Yes, the readme states that the ESM syntax is supported as well as the require form, with the same default export and the same method calls returning an object with a data property.

Which languages does the api generator produce SDKs for?

The codegen offering creates an SDK for TypeScript or JavaScript, with TypeScript types included. The documentation does not describe generators for other target languages.

How is the api project versioned and released?

The repository is an npm workspaces monorepo built with turbo and published with changesets, with a version-packages script running changeset version and a release script running changeset publish. The newest published tag is v7.0.0-beta.13 from 2025-04-04.

What licence is the api library released under?

MIT, with the licence file at the root of the repository and a LICENSES/ directory alongside it for third-party code.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. readmeio/api on GitHub
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/readmeio-api.svg)](https://hysenlabs.com/projects/readmeio-api)

Was this useful?