Generating Client SDKs from OpenAPI

An API that ships an OpenAPI spec but no client leaves every consumer to hand-roll their own HTTP calls, drift out of sync when the API changes, and rediscover the same bugs independently. Generating typed client SDKs from the spec — and surfacing them in the portal next to the API — turns “read the docs and write a fetch” into “install the package”. This how-to generates, versions, and publishes client SDKs from your OpenAPI definitions. It extends Internal API Portals & Discovery within Developer Experience & Self-Service Platforms.

Prerequisites

  • Backstage 1.20+ with APIs registered in the catalog and their OpenAPI specs attached.
  • A valid, linted OpenAPI 3.x spec per API — generation is only as good as the spec.
  • A generator such as openapi-generator or openapi-typescript, runnable in CI.
  • A package registry to publish SDKs to, and a versioning convention tied to the API version.
Prerequisites for SDK generation Registered APIs, a linted spec, a generator, and a registry enable typed client SDK generation. registered APIs linted OpenAPI spec generator in CI package registry Typed clients install, don't roll
A linted spec is non-negotiable — the generator faithfully reproduces every gap and ambiguity in your OpenAPI into the client.

Exact Configuration

  1. Generate the SDK from the spec in CI.

    # Requires openapi-generator-cli
    openapi-generator-cli generate \
      -i specs/payments.openapi.yaml \
      -g typescript-fetch \
      -o clients/payments \
      --additional-properties=npmName=@internal/payments-client,supportsES6=true
    
  2. Version the SDK to match the API, so a consumer can pin a client to an API version.

    {
      "name": "@internal/payments-client",
      "version": "1.4.0",
      "peerDependencies": {}
    }
    
  3. Link the SDK from the API entity so developers find it where they find the API.

    # catalog-info.yaml (the API entity)
    metadata:
      name: payments-api
      links:
        - url: https://npm.internal/@internal/payments-client
          title: TypeScript client SDK
          icon: code
    
Spec to published SDK The OpenAPI spec feeds the generator, which produces a versioned client that is published and linked from the API entity. OpenAPI spec generator in CI versioned SDK published linked on API entity discoverable
Regenerating on every spec change in CI is what keeps the client honest — a hand-maintained SDK drifts the moment the API moves.

Validation

# Requires the generated client, node, curl
# 1. The generated client compiles
cd clients/payments && npm install && npx tsc --noEmit
# Expected: no type errors

# 2. The SDK version tracks the API version
jq -r '.version' clients/payments/package.json
grep -m1 'version:' specs/payments.openapi.yaml
# Expected: aligned major.minor

# 3. The API entity links to the SDK
curl -s "${PORTAL_URL}/api/catalog/entities/by-name/api/default/payments-api" \
  | jq '[.metadata.links[] | select(.title | test("client"))] | length'
# Expected: >= 1
SDK generation validation checks The client compiles, its version tracks the API, and the API entity links to it. Compiles no type errors Version tracks API aligned Linked on the entity
A client that compiles against the current spec is proof the two are in sync — a type error after regeneration is an early warning of a breaking API change.

Edge Cases & Troubleshooting

Symptom Root Cause Resolution
Generated client won’t compile Spec has invalid or ambiguous types Lint and fix the OpenAPI before generating
SDK out of sync with API Manual generation, not in CI Regenerate on every spec change in CI
Breaking change ships silently No version bump discipline Bump the SDK major when the API breaks
Consumers pin to nothing Unversioned “latest” only Publish semver and encourage pinning
SDK undiscoverable Not linked from the API entity Add a links entry to catalog-info.yaml
SDK generation pitfalls by class Compile failures and drift are generation issues; silent breaks and undiscoverable SDKs are versioning and discovery issues. Generation won't compile drift → lint + regen in CI Version & discovery silent break undiscoverable → semver + link entity
Generation from a bad spec produces a bad client silently — the lint step is the cheapest place to catch it, long before consumers do.

Frequently Asked Questions

Which languages should I generate for?

The ones your consumers actually write in — usually one or two, not the full matrix the generator supports. A TypeScript client and a Go client that people use beat six languages nobody installs. Start from real consumer demand, and add a language when a team asks, not speculatively.

Should the SDK be generated or hand-written?

Generated, kept thin, with any ergonomics layered in a hand-written wrapper on top. A generated core stays honest with the spec automatically; a hand-written client drifts the first time someone forgets to update it after an API change. If the generated output is awkward, wrap it — do not fork it.