Guide

OpenAPI to npm package

Go from an OpenAPI URL to a versioned npm package on your registry — public, private, or custom — every time the schema changes.

Last reviewed: April 23, 2026

"Generate an SDK from OpenAPI" is a query that hides a second question: where does the SDK actually live? A folder of generated code in a subdirectory of your API repo is not an SDK a consumer can install. It becomes an SDK the moment it is a published, versioned npm package on a registry the consumer has access to.

That last mile — the npm lifecycle — is where most OpenAPI-to-TypeScript workflows quietly stall. The codegen step is easy. Owning the npm identity, deciding on a versioning policy, wiring a private registry, rotating tokens, and making all of that repeat on every schema change is what eats the week.

SDK Factory is built around the npm-lifecycle half of the problem. Paste the OpenAPI URL, pick a package name, pick a registry (public npm, GitHub Packages, or a custom endpoint with a token), and the pipeline handles everything from "spec is now different" to "new tarball on the registry."

Why the manual path hurts

What you actually spend time on when you wire this by hand — and what eventually drifts.

Every codegen tool stops at source code

openapi-generator, openapi-typescript, swagger-codegen — they all hand you a directory of files. Building that into a tarball, bumping the version, and running `npm publish` is glue your team has to write and maintain. Forever.

Version numbers are your problem

Semver against an OpenAPI diff is not a solved problem. Most teams punt and use `--patch` on every rebuild, which is wrong when the schema drops an endpoint. Others cut versions by hand and forget to bump. Both end up with a registry full of meaningless numbers.

Private registry setup is an afternoon

GitHub Packages needs a scoped token and an `.npmrc`. JFrog Artifactory needs a different `.npmrc`. A custom Verdaccio instance behind corporate SSO needs a third. Every generator leaves this to you — and every rotation leaves it broken until someone notices.

Tokens rot quietly

npm tokens expire. GitHub PATs get revoked. The CI step still runs, still fails silently, and you only find out when a consumer asks why the latest version is six weeks stale.

Before and after

The hand-rolled version versus the pasted-URL version — same end state, very different footprint.

Doing it by handCI + generated package metadata
# .github/workflows/publish-sdk.yml
name: publish-sdk
on:
  push: { branches: [main] }
  schedule: [{ cron: "0 */6 * * *" }]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://npm.pkg.github.com
      - run: npm install -D @openapitools/openapi-generator-cli
      - run: |
          npx openapi-generator-cli generate -i ./openapi.yaml \
            -g typescript-axios -o ./sdk \
            --additional-properties=npmName=@acme/api-client
      - run: cd sdk && npm install && npm run build
      - run: cd sdk && npm version patch --no-git-tag-version
      - run: cd sdk && npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# + a rotation SOP for the token. + retention for old versions.
# + a human to review the diff before every publish.
With SDK FactorySDK Factory dashboard
Paste schema URL:  https://api.example.com/openapi.yaml

Registry:          [ npm public ▼ ]
                   [ npm private  ]
                   [ GitHub Pkgs  ]
                   [ custom…      ]

Package name:      @acme/api-client
Token (encrypted): ••••••••••••

┌────────────────────────┐
│   Save & auto-publish  │
└────────────────────────┘

→ Token stored AES256-encrypted, rotatable.
→ Version read from openapi.info.version — no guessing.
→ Every tarball archived in S3 for audit / re-upload.

What SDK Factory does instead

The same pain points, handled by the pipeline — not by whoever is on call this week.

npm publish is a dashboard toggle

Pick `npm (public)`, `npm (private)`, GitHub Packages, or `custom` with a URL. Paste the token once — we encrypt it at rest (AES256, rotateable) — and the pipeline owns publish from then on.

Version is the spec's version

We read `openapi.info.version` and publish under that. You own the bump, in the same place you already own the API. No semver guessing, no drift between what your API says and what the registry shows.

First-class private registries

GitHub Packages, JFrog, Verdaccio, any registry that speaks the npm protocol — configured in one form, not a 40-line `.npmrc` hidden in CI. Tokens are stored per-app, rotatable from the dashboard.

Tarballs are retained

Every published tarball is stored in S3 keyed by deployment ID, separate from the registry itself. If your registry loses a version (unpublish, retention policy, mis-targeted delete), we can re-upload the exact artifact that shipped.

Versioning strategy we actually recommend

The cleanest versioning story for an OpenAPI-driven SDK is: the SDK's version number equals the API's version number. If your spec says `info.version: 1.7.2`, the published package is 1.7.2. When you bump the spec, you bump the SDK — in one place.

That only works if your team treats `info.version` as a real release number, not decoration. If nobody owns bumping it, any tool that generates an SDK will reproduce that ambiguity downstream. SDK Factory surfaces the most recently published version in the dashboard so spec drift is visible.

We deliberately do not infer semver from the schema diff. Diff-based bumping sounds automatic and is wrong for about a third of real changes — renames read as "added + removed," optional-becoming-required reads as additive, and so on. You're better placed to decide major/minor/patch than any heuristic.

Registry targets

Public npm is the default. The token is an automation token on the account that owns the package name — we store it encrypted, and rotation is a form-submit, not an infra ticket.

GitHub Packages is a first-class target, not a side note. Paste a GitHub PAT with `write:packages`, pick the scope, and the pipeline handles scoped publish + the `publishConfig` rewrite the registry needs. Readers on the consumer side still configure their own `.npmrc`, but the publish side stops being your problem.

Custom registries (JFrog, Verdaccio, Sonatype, AWS CodeArtifact, Cloudsmith) plug in via a URL + token. We only require the registry to speak the standard npm protocol — no vendor-specific flags. If it accepts `npm publish`, it works.

Token storage and rotation

Every registry token is encrypted with AES-256 before storage, with the envelope key separate from the database. The dashboard never echoes a token back in plaintext; the only supported operation is rotate-and-replace.

Rotation is a first-class action because forgotten tokens are the single most common cause of "my SDK stopped updating six weeks ago and nobody noticed." When a publish fails with a 401, the app moves to REQUIRE_ACTION and you see it on the dashboard — instead of reading a CI log for the first time during an incident.

Who reaches for this

  • Teams who generate SDK code fine, but publishing it to GitHub Packages / JFrog / Verdaccio keeps breaking on token rotation.
  • Organisations where the API version (in `openapi.info.version`) is already the source of truth and they want the SDK to mirror it.
  • Product companies who want to offer a typed client to customers without standing up an internal SDK-maintenance function.
  • Internal platform teams publishing one package per downstream service, on a shared private registry, without 20 bespoke CI pipelines.

FAQ

Can I publish to more than one registry from the same schema?

Today each app publishes to a single registry. If you need a dual-publish setup (e.g. GitHub Packages for internal + public npm for customers), create two apps pointed at the same schema URL with different package names / scopes. Both will rebuild on every schema change.

What happens if the token is rotated externally?

The next publish fails with a 401. The app moves to REQUIRE_ACTION and stops polling the schema until the token is refreshed. The last-good published version stays on your registry — consumers don't see a regression.

Do you support npm scopes (`@org/package`)?

Yes. Scoped packages are first-class on every registry target. If the scope is a GitHub organisation, the repository the package claims to belong to is read from `info.x-repository` if present, or left blank otherwise.

How fast does a schema change land on the registry?

We poll every few minutes. Once a change is detected the build + publish pipeline typically takes 1–3 minutes. Worst case, a schema change is live on your registry within five minutes of the spec being served from the URL.

Can I trigger a publish without a schema change?

Yes, from the dashboard ("rebuild from current schema"). Useful when you want to re-cut a version after a tooling upgrade on our end, or to replay an older schema snapshot. Normal operation is fully passive — you shouldn't need to.

Try it with your actual schema.

One app on the Free tier, no card required. Paste your OpenAPI URL and see the generated npm package in minutes.