Engineering

How to publish an OpenAPI SDK to PyPI

PyPI trusted publishing lets a GitHub Actions workflow publish a Python package without a long-lived PyPI API token. For OpenAPI-generated Python SDKs, that is the right default.

The safer workflow is:

  1. Generate the Python SDK from OpenAPI.
  2. Build wheel and sdist artifacts.
  3. Install the wheel in a clean environment.
  4. Run import and smoke tests.
  5. Show package metadata and compatibility report.
  6. Wait for approval.
  7. Publish through the PyPI trusted publisher.

Nothing in that sequence requires handing a broad PyPI token to a generator.

What PyPI needs

PyPI trusted publishing connects a specific project to a specific GitHub repository, workflow file, environment, and owner. The usual checklist:

  • PyPI project exists or the first publish is approved for that name.
  • You have added a Trusted Publisher to an existing PyPI project, or configured a pending publisher before the first publish for a new project name.
  • GitHub repository is the intended source.
  • Workflow file path is stable, for example .github/workflows/publish-python.yml.
  • If PyPI has an environment configured, the GitHub Actions job uses the same environment:.
  • The workflow has id-token: write.
  • Package metadata in pyproject.toml is correct.
  • The distribution builds locally.

A minimal publish job looks like this:

name: Publish Python SDK
on:
  workflow_dispatch:
    inputs:
      version:
        required: true
        type: string

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: python -m pip install build twine
      - run: python -m build
      - run: python -m twine check dist/*
      - uses: pypa/gh-action-pypi-publish@release/v1

With trusted publishing configured, PyPI trusts the OIDC identity of that workflow.

Why generated Python SDKs need extra checks

Python package failures often show up after publish:

  • module name differs from package name
  • wheel installs but imports fail
  • async client imports optional dependencies incorrectly
  • pyproject.toml metadata is incomplete
  • generated models require a newer Python than advertised
  • README renders poorly on PyPI

Sourced's PyPI readiness flow catches these before publish. It builds the package, installs it, imports it, and reports what is missing from trusted publisher setup.

What should be free

Previewing a generated Python SDK should be free. So should seeing whether the package builds and whether docs can be reviewed.

In Sourced, the free tier includes unlimited previews and up to 2 hosted noindex docs review sites. Upgrade to Launch when you want repo sync, registry readiness/approved publishing, production docs on Sourced-controlled URLs, team controls, or longer retention; use Scale when you need production custom domains and more project capacity.

That keeps the user decision clean: review output first, pay when the release becomes production.

Common trusted publisher errors

When PyPI publishing fails, search for:

  • "trusted publisher not configured"
  • "invalid-publisher"
  • "id-token: write missing"
  • "project name already taken"
  • "twine check failed"
  • "No module named"

Most of these are setup issues, not generator issues. A good release readiness page should tell the user exactly which one is missing and link to the place to fix it.

What Sourced does

Sourced generates the Python SDK preview, builds and checks the package, and reports what is missing before PyPI publishing. Free covers evaluation: unlimited previews and up to 2 hosted noindex docs review sites. Launch is $99/month per API project for repo sync, registry readiness/approved publishing, production docs on Sourced-controlled URLs, and team review. Scale is $349/month and adds more API projects, more hosted docs sites, more team capacity, and production custom domains.

Read the Python SDK guide or see client packages.