Night Mode LabsBlue Book
Recipes

API-to-SDK Regeneration with GitHub Actions

Create bot-authored SDK regeneration pull requests from API changes.

This recipe shows how to keep a generated SDK repository in sync with a REST API repository. When an API pull request merges to main, the API pipeline validates the backend, dispatches a workflow in the SDK repository, and the SDK repository regenerates its OpenAPI document and client code from the exact API commit.

The result is a bot-authored SDK pull request with a clear source commit, OpenAPI change summary, SemVer impact, generated code, and release-ready package version.

When to use this recipe

Use this pattern when:

  • The API and SDK live in separate repositories.
  • The API exposes an OpenAPI schema generated from server code.
  • The SDK is generated from that OpenAPI schema.
  • SDK consumers need reviewable pull requests instead of direct commits.
  • You want release management to happen from the SDK repository, not from the API repository.

Do not use this pattern when the SDK is hand-written, when the API and SDK are published from one monorepo pipeline, or when consumers do not need a versioned SDK artifact.

Target outcome

After implementation:

  1. API merges to main run API checks and export OpenAPI.
  2. API CI dispatches SDK CI with only metadata.
  3. SDK CI checks out the exact API commit.
  4. SDK CI exports OpenAPI from that API commit.
  5. SDK CI regenerates the SDK from the exported schema.
  6. SDK CI classifies the OpenAPI change as major, minor, patch, or none.
  7. SDK CI chooses the matching Conventional Commit semantics.
  8. SDK CI opens or updates a GitHub Actions bot pull request.
  9. SDK CI labels the pull request with generated and semver-*.
  10. python-semantic-release publishes after the bot pull request merges.

🤖 Reusable agent prompt

Use this prompt when you want an AI coding agent to stand up this recipe quickly for a pair of repositories. It is written to produce a complete implementation, not just a sketch.

End-to-end verification can write to GitHub

This prompt includes verification steps that may push repositories, configure GitHub secrets, trigger workflows, merge pull requests, create Git tags, create GitHub releases, and publish package artifacts. Require explicit user approval before any remote write, merge, tag, release, or registry publish.

Inputs to collect first

Before running the prompt, infer as many values as possible from the repositories and ask only for missing values, credentials, organization settings, or choices with multiple valid options.

Inputs to resolve:

InputExample
Organizationacme-org
API repositoryacme-api
SDK repositoryacme-sdk
API frameworkFastAPI
API OpenAPI export commandmise run openapi
SDK languagePython
SDK generatoropenapi-python-client
Generated SDK output pathgenerated/acme-api-client
Package nameacme-api-client
Import package nameacme_api_client
GitHub App nameacme-sdk-regenerator

The prompt assumes the repositories already exist and the agent has local checkouts or can clone them.

Copy-paste implementation prompt

You are working in two GitHub repositories.

Before editing files, read the canonical recipe at:
https://bluebook.nightmode.dev/docs/api-to-sdk-regeneration

Treat that page as the source of truth for the implementation order,
workflow structure, verification process, and code snippets. Follow the
numbered steps on the page deterministically and adapt the snippets only
where repository names, package names, paths, or existing project
conventions require it.

First inspect both repositories and infer every input you can. Ask the
user only for missing values, credentials, organization settings, or
choices with multiple valid options before editing files.

- API repository: <ORG>/<API_REPO>
- SDK repository: <ORG>/<SDK_REPO>

Goal: implement a production-ready API-to-SDK regeneration workflow.
When a pull request merges to API main, API CI should validate the API,
verify OpenAPI export, dispatch SDK CI with only source metadata, and the
SDK repository should checkout the exact API commit, export OpenAPI,
regenerate the SDK, classify the OpenAPI change, create a Conventional
Commit for semantic-release, and open or update a bot-authored SDK pull
request.

Use this architecture:

1. API repo owns API tests and OpenAPI export.
2. SDK repo owns code generation, SDK tests, SemVer classification,
   generated pull requests, semantic-release, and SDK releases.
3. The API workflow must not pass the whole OpenAPI schema as a dispatch
   payload. It must pass only:
   - source API repository
   - source API commit SHA
   - source API workflow run URL
4. The SDK workflow must checkout the API repository at the exact source
   SHA and run the API OpenAPI export command itself.
5. Pull requests in the SDK repo must be created by `github-actions[bot]`
   or `app/github-actions`, not by a personal token.
6. Use a GitHub App for cross-repository credentials.
7. Keep human PATs only as a temporary fallback during migration, then
   remove them after app-token verification.

Repository assumptions:

- API default branch: main
- SDK default branch: main
- Tool runner: mise
- Python package manager: uv
- API OpenAPI export task: mise run openapi
- API quality gates:
  - mise run sync
  - mise run lint
  - mise run test
  - mise run openapi
- SDK quality gates:
  - mise run sync
  - mise run generate
  - mise run lint
  - mise run test

Implement the following in the API repo:

1. Ensure there is a deterministic OpenAPI export script and a mise task:
   - task name: openapi
   - output: openapi.json
   - no development server required
2. Add `.github/workflows/update-sdk.yml`.
3. The API workflow must:
   - run on push to main and workflow_dispatch
   - checkout API
   - install mise tools
   - run API quality gates
   - create a GitHub App installation token for the SDK repo using:
     - repo variable: SDK_APP_ID
     - repo secret: SDK_APP_PRIVATE_KEY
   - dispatch the SDK workflow with repository_dispatch event type:
     `openapi-schema-updated`
   - send source_api_repo, source_api_sha, and source_api_run_url only
4. Do not commit generated OpenAPI to the API repo unless the existing
   project already has that convention.

Implement the following in the SDK repo:

1. Ensure code generation is deterministic from checked-in or copied
   `openapi.json`.
2. Add or update `.github/workflows/update-from-api.yml`.
3. The SDK workflow must:
   - run on repository_dispatch event type `openapi-schema-updated`
   - support workflow_dispatch with source_api_repo and source_api_sha
   - checkout the SDK repo
   - create a GitHub App installation token for the API repo using:
     - repo variable: API_APP_ID
     - repo secret: API_APP_PRIVATE_KEY
   - checkout the API repo at source_api_sha
   - run API quality gates and export OpenAPI from that commit
   - copy the API-generated openapi.json into the SDK repo
   - run SDK generation
   - analyze OpenAPI changes and produce a Conventional Commit subject
   - run SDK lint and tests
   - push branch `bot/update-openapi-from-restapi`
   - create or update an SDK PR with GitHub Actions token
   - add labels `generated` and one of:
     - semver-major
     - semver-minor
     - semver-patch
     - semver-none
   - include source API commit, source API workflow run, OpenAPI change
     summary, SemVer impact, next SDK version, and checks run in the PR
     body
4. Add `.github/workflows/ci.yml` for normal SDK lint and tests.
5. Add `python-semantic-release` configuration and
   `.github/workflows/release.yml` that, on SDK main changes to the
   generated SDK or openapi.json:
   - reads Conventional Commits since the last `sdk-vX.Y.Z` tag
   - stamps `generated/acme-api-client/pyproject.toml`
   - updates `CHANGELOG.md`
   - creates tag and GitHub release `sdk-vX.Y.Z`
   - builds wheel and source distribution
   - uploads distributions to the GitHub release
   - publishes to PyPI only if PYPI_API_TOKEN is configured
6. Add tests that prove generated SDK model fields cover the OpenAPI
   schema properties for at least one representative model.
7. Add unit tests for the OpenAPI change classification logic.

Versioning policy:

- major: removed path, operation, schema, property, response code, or
  added required request property
- minor: added path, operation, schema, or optional property
- patch: metadata-only OpenAPI change
- none: no OpenAPI change

Security and release-management requirements:

- Prefer GitHub App tokens over personal access tokens.
- Use least-privilege app installation on only the API and SDK repos.
- Never print private keys, tokens, or generated installation tokens.
- Do not publish SDK artifacts from the API workflow.
- Release only from SDK main after the generated SDK PR is reviewed and
  merged.
- Keep generator upgrades separate from API schema changes.
- Make every generated PR traceable to the source API commit.

Verification steps:

1. Run local format, lint, and tests in both repositories.
2. Validate workflow YAML syntax.
3. Push both repositories.
4. Configure GitHub App variables and secrets:
   - API repo variable: SDK_APP_ID
   - API repo secret: SDK_APP_PRIVATE_KEY
   - SDK repo variable: API_APP_ID
   - SDK repo secret: API_APP_PRIVATE_KEY
5. Trigger the API workflow manually once.
6. Confirm API workflow dispatches SDK workflow successfully.
7. Confirm SDK workflow checks out the API commit and succeeds.
8. Make a schema-visible API change in a PR, merge it to API main, and
   confirm an SDK PR is created by GitHub Actions.
9. Confirm the SDK PR includes generated code, openapi.json, SemVer label,
   next version, source commit link, and passing checks.
10. Merge the SDK PR and confirm semantic-release creates a version
    commit, tag, changelog entry, GitHub release, and artifacts.

Do not stop at writing workflow files. Test the end-to-end flow against
GitHub and fix failures until the flow works.

Expected repository settings

After the implementation, each repository should have these settings.

API repository

NameTypePurpose
SDK_APP_IDvariableGitHub App ID used to dispatch SDK workflow.
SDK_APP_PRIVATE_KEYsecretPrivate key for SDK app token creation.

SDK repository

NameTypePurpose
API_APP_IDvariableGitHub App ID used to checkout API source.
API_APP_PRIVATE_KEYsecretPrivate key for API app token creation.
PYPI_API_TOKENsecretOptional token for PyPI publishing.

The organization must also allow GitHub Actions to create pull requests:

Organization settings → Actions → General → Workflow permissions →
Allow GitHub Actions to create and approve pull requests

What good output looks like

A successful generated SDK pull request should have:

  • author: app/github-actions or github-actions[bot]
  • branch: bot/update-openapi-from-restapi
  • title with next SDK version
  • source API commit link
  • source API workflow run link
  • OpenAPI change summary
  • SemVer impact
  • generated code changes
  • openapi.json changes
  • Conventional Commit subject matching the SemVer impact
  • labels: generated and semver-*
  • a passing regeneration check

A successful release should have:

  • tag: sdk-vX.Y.Z
  • semantic-release version commit
  • wheel artifact
  • source distribution artifact
  • optional PyPI publication when configured

Common follow-up prompts

Use these if the first implementation is close but incomplete.

The SDK PR is authored by my user, not GitHub Actions. Refactor so the
SDK repository workflow creates the PR with `${{ github.token }}` and the
API repository only dispatches metadata.
The API workflow currently passes the full OpenAPI JSON in the dispatch
payload. Refactor so it passes only repo, SHA, and run URL. The SDK
workflow should checkout the API commit and export OpenAPI itself.
Add release management: SemVer classification, Conventional Commit PR
commits, python-semantic-release on merge, PR labels, SDK artifacts, and
checks that prove generated models match the OpenAPI schema.
Replace the remaining personal access token with GitHub App credentials.
Use actions/create-github-app-token and remove PAT fallback after the
workflow is verified.

Repository layout

The recipe assumes two repositories:

RepositoryPurpose
acme-apiFastAPI or another REST API source repository.
acme-sdkGenerated SDK source, tests, and release workflow.

The names are examples. Replace them with the client or product names used in the engagement.

High-level flow

The API workflow does not send the OpenAPI file as a payload. It sends only:

  • source API repository
  • source API commit SHA
  • source API workflow run URL

The SDK workflow recreates the OpenAPI file from the checked-out API commit. That makes the result reproducible and keeps dispatch payloads small.

Prerequisites

You need:

  • GitHub organization administration access, or a partner who has it.
  • A GitHub App installed on the API and SDK repositories.
  • A way for the API to export an OpenAPI document in CI.
  • A deterministic SDK generation command.
  • Branch protection on the SDK repository, or at least review discipline around generated pull requests.

The examples below use:

  • mise for tool orchestration
  • uv for Python dependency and package operations
  • openapi-python-client for Python SDK generation
  • GitHub Actions for CI
  • GitHub Releases as the default SDK release target

Implementation steps

Step 1: make OpenAPI export deterministic

The API repository should provide a command that exports the OpenAPI schema without running a development server.

For FastAPI, create a small script like this:

"""Export the API OpenAPI document to disk."""

import json
import sys
from pathlib import Path

from acme_api import create_app


def main() -> None:
    """Write the OpenAPI schema to disk."""
    output_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(
        "openapi.json",
    )
    app = create_app()
    output_path.write_text(
        json.dumps(app.openapi(), indent=2, sort_keys=True) + "\n",
        encoding="utf-8",
    )


if __name__ == "__main__":
    main()

Expose it through mise:

[tasks.openapi]
description = "Export the API OpenAPI document."
run = "uv run python scripts/export_openapi.py openapi.json"

The command should be stable across runs. Avoid environment-specific servers, credentials, dynamic timestamps, or feature flags that change the schema without changing code.

Step 2: make SDK generation deterministic

The SDK repository should generate from a checked-in or copied openapi.json file.

Example mise tasks:

[tasks.generate]
description = "Regenerate the Python SDK from openapi.json."
run = """
rm -rf generated/acme-api-client
uv run openapi-python-client generate \
  --path openapi.json \
  --config openapi-python-client.yaml \
  --meta uv \
  --output-path generated/acme-api-client \
  --overwrite
"""

[tasks.lint]
description = "Run strict Ruff checks."
run = "uv run ruff check ."

[tasks.test]
description = "Run SDK tests."
run = "uv run pytest"

Generated code should be treated as generated-only. Do not hand-edit files under the generated output directory. Put custom helpers outside that directory, or provide custom templates through the code generator.

Step 3: create a GitHub App

Create one GitHub App for the cross-repository automation.

Recommended settings:

SettingValue
WebhookInactive, unless the engagement needs webhooks.
InstallationOnly the API and SDK repositories.
ContentsRead and write.
Pull requestsRead and write.
ActionsRead and write.
Commit statusesRead and write.
MetadataRead-only.

Generate a private key and record the app ID.

Install the app on both repositories. Keeping the installation limited to only these repositories reduces blast radius if the private key is ever rotated after exposure.

Step 4: configure repository variables and secrets

In the API repository, store:

NameTypeValue
SDK_APP_IDvariableGitHub App ID.
SDK_APP_PRIVATE_KEYsecretApp private key PEM.

In the SDK repository, store:

NameTypeValue
API_APP_IDvariableGitHub App ID.
API_APP_PRIVATE_KEYsecretApp private key PEM.

The same GitHub App can be used for both directions if it is installed on both repositories with the permissions listed above.

Use repository secrets, not organization secrets, unless the same app is intended to automate multiple repository pairs.

Step 5: add the API dispatch workflow

Create .github/workflows/update-sdk.yml in the API repository.

name: Update generated SDK

on:
  push:
    branches:
      - main
  workflow_dispatch:

concurrency:
  group: update-generated-sdk-${{ github.ref }}
  cancel-in-progress: false

permissions:
  contents: read

env:
  SDK_REPOSITORY: acme-org/acme-sdk

jobs:
  update-sdk:
    name: Dispatch API commit to SDK
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout REST API
        uses: actions/checkout@v4
        with:
          persist-credentials: false

      - name: Install mise tools
        uses: jdx/mise-action@v2
        with:
          install: true
          cache: true

      - name: Verify API can export OpenAPI schema
        run: |
          mise trust .mise.toml
          mise run sync
          mise run lint
          mise run test
          mise run openapi

      - name: Create SDK repository app token
        id: sdk-app-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ vars.SDK_APP_ID }}
          private-key: ${{ secrets.SDK_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: acme-sdk

      - name: Dispatch SDK regeneration workflow
        env:
          GH_TOKEN: ${{ steps.sdk-app-token.outputs.token }}
        run: |
          gh api \
            --method POST \
            "repos/${SDK_REPOSITORY}/dispatches" \
            -f event_type='openapi-schema-updated' \
            -f client_payload[source_api_repo]="${GITHUB_REPOSITORY}" \
            -f client_payload[source_api_sha]="${GITHUB_SHA}" \
            -f client_payload[source_api_run_url]="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"

This workflow is intentionally simple. It verifies that the API can export OpenAPI, then dispatches the SDK repository with enough information to reproduce the generated output.

The API workflow does not wait for the SDK workflow to finish. If the engagement requires a single red or green signal on the API merge, add a polling step that waits for the SDK workflow run. Most teams keep these signals separate: API main stays green when dispatch succeeds, while SDK main and SDK pull requests own SDK generation health.

Step 6: add the SDK regeneration workflow

Create .github/workflows/update-from-api.yml in the SDK repository.

name: Update from REST API schema

on:
  repository_dispatch:
    types:
      - openapi-schema-updated
  workflow_dispatch:
    inputs:
      source_api_repo:
        required: true
        default: acme-org/acme-api
      source_api_sha:
        required: true
      source_api_run_url:
        required: false
        default: ""

concurrency:
  group: update-from-api-schema
  cancel-in-progress: false

permissions:
  contents: write
  pull-requests: write
  statuses: write

env:
  API_WORKDIR: api-repo
  SDK_UPDATE_BRANCH: bot/update-openapi-from-restapi

jobs:
  regenerate-sdk:
    name: Regenerate SDK and open PR
    runs-on: ubuntu-latest
    timeout-minutes: 15

The workflow needs three phases:

  1. resolve dispatch payload
  2. checkout SDK and API repositories
  3. regenerate, classify, commit, and open the pull request

A production implementation should include these steps:

- name: Resolve dispatch payload
  id: payload
  run: |
    if [ -n "${DISPATCH_SOURCE_API_REPO}" ]; then
      source_api_repo="${DISPATCH_SOURCE_API_REPO}"
      source_api_sha="${DISPATCH_SOURCE_API_SHA}"
      source_api_run_url="${DISPATCH_SOURCE_API_RUN_URL}"
    else
      source_api_repo="${INPUT_SOURCE_API_REPO}"
      source_api_sha="${INPUT_SOURCE_API_SHA}"
      source_api_run_url="${INPUT_SOURCE_API_RUN_URL}"
    fi

    if [ -z "${source_api_repo}" ]; then
      echo "Source API repo is required." >&2
      exit 1
    fi

    if [ -z "${source_api_sha}" ]; then
      echo "Source API SHA is required." >&2
      exit 1
    fi

    {
      echo "source_api_repo=${source_api_repo}"
      echo "source_api_sha=${source_api_sha}"
      echo "source_api_run_url=${source_api_run_url}"
    } >> "${GITHUB_ENV}"

    {
      echo "source_api_repo=${source_api_repo}"
      echo "source_api_sha=${source_api_sha}"
      echo "source_api_run_url=${source_api_run_url}"
    } >> "${GITHUB_OUTPUT}"
  env:
    DISPATCH_SOURCE_API_REPO: ${{ github.event.client_payload.source_api_repo }}
    DISPATCH_SOURCE_API_SHA: ${{ github.event.client_payload.source_api_sha }}
    DISPATCH_SOURCE_API_RUN_URL: ${{ github.event.client_payload.source_api_run_url }}
    INPUT_SOURCE_API_REPO: ${{ inputs.source_api_repo }}
    INPUT_SOURCE_API_SHA: ${{ inputs.source_api_sha }}
    INPUT_SOURCE_API_RUN_URL: ${{ inputs.source_api_run_url }}

Then checkout both repositories:

- name: Checkout SDK
  uses: actions/checkout@v4
  with:
    fetch-depth: 0
    path: sdk-repo
    persist-credentials: false

- name: Create API repository app token
  id: api-app-token
  uses: actions/create-github-app-token@v2
  with:
    app-id: ${{ vars.API_APP_ID }}
    private-key: ${{ secrets.API_APP_PRIVATE_KEY }}
    owner: ${{ github.repository_owner }}
    repositories: acme-api

- name: Checkout REST API source commit
  uses: actions/checkout@v4
  with:
    repository: ${{ steps.payload.outputs.source_api_repo }}
    ref: ${{ steps.payload.outputs.source_api_sha }}
    token: ${{ steps.api-app-token.outputs.token }}
    path: ${{ env.API_WORKDIR }}
    persist-credentials: false

Export the OpenAPI schema from the API checkout and regenerate the SDK:

- name: Export OpenAPI schema from API commit
  working-directory: ${{ env.API_WORKDIR }}
  run: |
    mise trust .mise.toml
    mise run sync
    mise run lint
    mise run test
    mise run openapi

- name: Regenerate SDK and analyze OpenAPI change
  id: openapi
  working-directory: sdk-repo
  run: |
    cp ../${API_WORKDIR}/openapi.json openapi.json
    mise trust .mise.toml
    mise run sync
    mise run generate
    uv run python scripts/analyze_openapi_change.py
    cat .openapi-change.env >> "${GITHUB_OUTPUT}"
    mise run lint
    mise run test

Commit the generated output and publish a status on the bot branch:

- name: Commit and push SDK update branch
  if: steps.changes.outputs.changed == 'true'
  working-directory: sdk-repo
  env:
    GH_TOKEN: ${{ github.token }}
    RELEASE_COMMIT_SUBJECT: ${{ steps.openapi.outputs.release_commit_subject }}
    RELEASE_COMMIT_FOOTER: ${{ steps.openapi.outputs.release_commit_footer }}
  run: |
    gh auth setup-git
    git config user.name "github-actions[bot]"
    git config user.email \
      "41898282+github-actions[bot]@users.noreply.github.com"
    git checkout -B "${SDK_UPDATE_BRANCH}"
    git add openapi.json generated
    commit_args=(
      -m "${RELEASE_COMMIT_SUBJECT}"
      -m "Source REST API commit: https://github.com/${source_api_repo}/commit/${source_api_sha}"
    )
    if [ -n "${RELEASE_COMMIT_FOOTER}" ]; then
      commit_args+=(-m "${RELEASE_COMMIT_FOOTER}")
    fi
    git commit "${commit_args[@]}"
    bot_sha=$(git rev-parse HEAD)
    git push --force-with-lease origin "${SDK_UPDATE_BRANCH}"
    gh api \
      --method POST \
      "repos/${GITHUB_REPOSITORY}/statuses/${bot_sha}" \
      -f state=success \
      -f context='SDK regeneration checks' \
      -f description='API export, SDK codegen, Ruff, and tests passed.' \
      -f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"

The explicit status is useful because pushes made with GITHUB_TOKEN do not always trigger normal pull request CI. It gives branch protection and reviewers a direct signal for the regeneration job that produced the commit.

Finally, create or update the pull request:

- name: Create or update SDK pull request
  if: steps.changes.outputs.changed == 'true'
  working-directory: sdk-repo
  env:
    GH_TOKEN: ${{ github.token }}
    SUMMARY_MARKDOWN: ${{ steps.openapi.outputs.summary_markdown }}
    NEXT_VERSION: ${{ steps.openapi.outputs.next_version }}
  run: |
    title="chore(sdk): regenerate from REST API schema (${NEXT_VERSION})"
    body_file=$(mktemp)
    {
      echo "Regenerated SDK from REST API main."
      echo
      echo "Source REST API commit:"
      echo "https://github.com/${source_api_repo}/commit/${source_api_sha}"
      echo
      printf '%s\n' "${SUMMARY_MARKDOWN}"
    } > "${body_file}"

    existing_pr=$(gh pr list \
      --head "${SDK_UPDATE_BRANCH}" \
      --base main \
      --state open \
      --json number \
      --jq '.[0].number // empty')

    if [ -n "${existing_pr}" ]; then
      gh pr edit "${existing_pr}" \
        --title "${title}" \
        --body-file "${body_file}"
      pr_number="${existing_pr}"
    else
      pr_url=$(gh pr create \
        --base main \
        --head "${SDK_UPDATE_BRANCH}" \
        --title "${title}" \
        --body-file "${body_file}")
      pr_number=$(gh pr view "${pr_url}" \
        --json number \
        --jq .number)
    fi

    gh pr edit "${pr_number}" \
      --add-label "generated,${{ steps.openapi.outputs.pr_label }}"

Keep the full workflow in source control rather than building it through the GitHub UI. The workflow itself is part of the release process and should be reviewed like application code.

Step 7: classify changes for semantic release

The SDK repository needs a small script that compares the new OpenAPI schema with the schema on SDK main. The script should classify the contract impact, but it should not own the final release commit. Let python-semantic-release stamp the package version, changelog, tag, and GitHub release after the generated SDK pull request merges.

Recommended default policy:

OpenAPI changeSDK bump
Removed path, operation, schema, or propertymajor
Added required request propertymajor
Added path, operation, schema, or optional propertyminor
Metadata-only changepatch
No schema changenone

The script should:

  • read openapi.json from the working tree
  • read the previous openapi.json from HEAD
  • read the current generated package version from HEAD
  • determine the SemVer impact
  • write Markdown summary output for the pull request body
  • write semver-* label output for the workflow
  • write a Conventional Commit subject for python-semantic-release
  • write a BREAKING CHANGE: footer for major releases

For Python SDKs, the generated package version usually lives in:

generated/acme-api-client/pyproject.toml

Keep this logic simple at first. OpenAPI breaking-change detection can be surprisingly deep, and a small deterministic policy is easier to trust than a complex rule set nobody understands. If the client has stricter contract requirements, replace this script with oasdiff, Optic, or an approved API compatibility checker.

Map the analysis result to Conventional Commits:

SemVer impactBot commit subject
majorfeat(sdk)!: regenerate from REST API schema
minorfeat(sdk): regenerate from REST API schema
patchfix(sdk): regenerate from REST API schema
nonechore(sdk): regenerate from REST API schema

This keeps release semantics reviewable in the SDK pull request while letting release automation use the same commit history developers see.

Step 8: add SDK CI

The SDK repository should still have a normal pull request CI workflow. Even if the regeneration workflow already ran tests, regular CI helps catch manual changes, dependency updates, and workflow edits.

name: CI

on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main

permissions:
  contents: read

jobs:
  quality:
    name: Lint and test
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout SDK
        uses: actions/checkout@v4

      - name: Install mise tools
        uses: jdx/mise-action@v2
        with:
          install: true
          cache: true

      - name: Run quality gates
        run: |
          mise trust .mise.toml
          mise run sync
          mise run lint
          mise run test

Branch protection should require the relevant SDK checks before SDK main can advance. If GitHub-token bot pushes do not trigger pull request CI, require the explicit SDK regeneration checks status or switch bot branch pushes to a GitHub App installation token that is allowed to trigger CI.

Step 9: connect SDK release automation

Release from the SDK repository after the generated SDK pull request is merged. Do not publish SDK artifacts from the API workflow.

For Python SDKs, use python-semantic-release as the release owner. The OpenAPI analyzer should decide the commit semantics, and semantic-release should turn those commits into a version, changelog, tag, GitHub release, and package artifacts.

The SDK regeneration workflow should emit Conventional Commit subjects that match the OpenAPI impact:

SemVer impactBot commit subject
majorfeat(sdk)!: regenerate from REST API schema
minorfeat(sdk): regenerate from REST API schema
patchfix(sdk): regenerate from REST API schema
nonechore(sdk): regenerate from REST API schema

Use the release workflow and pyproject.toml configuration from Python Package Versioning with Semantic Release.

Step 10: add schema-aware SDK tests

The SDK smoke test should verify more than importability. It should prove that generated models cover the current OpenAPI schema.

Example:

import json
from pathlib import Path

from attrs import fields

from acme_api_client.models import WidgetCreate


def test_widget_create_model_matches_openapi_schema() -> None:
    openapi = json.loads(Path("openapi.json").read_text(encoding="utf-8"))
    schema = openapi["components"]["schemas"]["WidgetCreate"]
    generated_field_names = {field.name for field in fields(WidgetCreate)}
    schema_property_names = set(schema["properties"])

    assert schema_property_names <= generated_field_names

For large SDKs, generate these assertions dynamically for all component schemas that map cleanly to model classes.

Operating model

A healthy operating model is:

  1. API team merges backend changes.
  2. SDK bot opens the generated SDK pull request.
  3. API and SDK owners review the SemVer impact.
  4. SDK pull request merges when checks are green.
  5. Semantic-release stamps the SDK version and changelog.
  6. SDK release workflow builds and publishes artifacts.
  7. Consumers upgrade SDK versions on their own cadence.

This keeps source ownership clear. API changes trigger SDK work, but SDK maintainers still own SDK releases. The API repository should not depend on the generated SDK. Application consumers, integration tests, CLIs, and external clients depend on the SDK.

SDK versioning and release management

Use SDK SemVer to communicate consumer impact, not API deployment impact. The generated SDK pull request should show the proposed SemVer impact and labels, but python-semantic-release should own the actual version bump after the pull request merges to SDK main.

Recommended policy:

  • major for breaking SDK or API contract changes
  • minor for additive API surface changes
  • patch for generated metadata, docs, or non-contract changes

For the full release workflow, changelog setup, GitHub release publishing, PyPI publishing, and consumer upgrade automation, see Python Package Versioning with Semantic Release.

Troubleshooting

The PR is authored by a human

The pull request was created with a personal access token. Move PR creation into the SDK repository workflow and use GH_TOKEN: ${{ github.token }}. The author should become github-actions[bot] or app/github-actions.

GitHub Actions cannot create the PR

The organization may block Actions from creating pull requests. Enable:

Organization settings → Actions → General →
Workflow permissions → Allow GitHub Actions to create and approve pull
requests

The SDK workflow cannot checkout the API repo

Check that the GitHub App is installed on the API repository and has Contents: Read access. Confirm the SDK repository has API_APP_ID and API_APP_PRIVATE_KEY configured.

The API workflow cannot dispatch the SDK workflow

Check that the GitHub App is installed on the SDK repository and has Actions: Write access. Confirm the API repository has SDK_APP_ID and SDK_APP_PRIVATE_KEY configured.

The SDK PR has no checks

Bot pushes made with GITHUB_TOKEN may not trigger pull request CI. Add a commit status from the regeneration workflow, or push the bot branch with a GitHub App token if the organization allows that event to trigger CI.

The version bump is wrong

Review the OpenAPI classification script and the generated Conventional Commit subject. If the client needs stricter contract checks, use a dedicated OpenAPI diff tool and map its output to SemVer labels and commit semantics.

The generated diff is huge

Separate API schema changes from generator upgrades. Pin generator versions in the SDK lockfile and upgrade the generator in its own pull request.

Definition of done

The recipe is complete when:

  • API main dispatches SDK regeneration successfully.
  • SDK regeneration checks out the exact API SHA.
  • SDK regeneration opens a bot-authored pull request.
  • The pull request includes OpenAPI and generated code changes.
  • The pull request includes source API commit and workflow links.
  • The pull request includes SemVer impact and next SDK version.
  • The pull request has generated and semver-* labels.
  • SDK tests pass against the generated client.
  • Merging the pull request creates a release artifact.
  • Human PAT secrets are removed after GitHub App verification.

On this page