Automating SDK Releases with the Speakeasy GitHub Action

This guide wires up the Speakeasy GitHub Action so that every change to your OpenAPI spec automatically regenerates your SDKs, opens a review pull request, and cuts a tagged release. It is part of the Speakeasy SDK generation section and the broader SDK Generation & Changelog Automation framework. The two moving parts are .speakeasy/workflow.yaml, which declares your spec source and SDK targets, and the speakeasy-api/sdk-generation-action, which runs that workflow in CI.

Speakeasy automated release pipeline A spec change triggers the Speakeasy GitHub Action, which regenerates the SDK, opens a pull request, and cuts a tagged release on merge. spec change openapi.yaml Speakeasy Action run-workflow regen PR mode: pr tagged release on merge

Problem & Context

When SDKs are generated by hand, the spec and the published clients drift. An engineer ships a new endpoint, forgets to regenerate, and the SDK lags by a release or two — or worse, someone regenerates locally and commits an SDK built from an uncommitted spec. The fix is to make regeneration a CI event tied to the spec, not a manual chore tied to memory.

The Speakeasy GitHub Action does exactly this. It reads .speakeasy/workflow.yaml, regenerates every SDK target, and — depending on mode — either opens a pull request you can review or commits directly. When the generated SDK actually changes, it bumps the version, tags the release, and publishes. This guide assumes you already have an OpenAPI spec; for a comparison with the configuration-file approach, see configuring Fern for multiple languages.

Step-by-Step Solution

1. Install the Speakeasy CLI

Install locally so you can author and test the workflow before pushing CI.

curl -fsSL https://go.speakeasy.com/cli-install.sh | sh
speakeasy --version

Expected output:

1.517.3

2. Create .speakeasy/workflow.yaml

Generate the scaffold with the interactive setup, which writes both the workflow and a .speakeasy/gen.yaml per target.

speakeasy quickstart

Then confirm .speakeasy/workflow.yaml declares your source spec and at least one SDK target:

# .speakeasy/workflow.yaml
workflowVersion: 1.0.0
speakeasyVersion: 1.517.3
sources:
  acme-api:
    inputs:
      - location: ./openapi.yaml
targets:
  typescript-sdk:
    target: typescript
    source: acme-api
    publish:
      npm:
        token: $NPM_TOKEN
  python-sdk:
    target: python
    source: acme-api
    publish:
      pypi:
        token: $PYPI_TOKEN

3. Validate the workflow locally

Run the workflow once on your machine to confirm the spec resolves and the SDKs generate.

speakeasy run

Expected output:

│ Workflow - success
│   └─Target: typescript-sdk - success
│   └─Target: python-sdk - success

4. Store the Speakeasy API key as a secret

The action needs SPEAKEASY_API_KEY to pull your workspace config. Copy it from the Speakeasy dashboard, then add it (plus your registry tokens) under the repository’s Settings → Secrets and variables → Actions.

gh secret set SPEAKEASY_API_KEY --body "$SPEAKEASY_API_KEY"
gh secret set NPM_TOKEN --body "$NPM_TOKEN"
gh secret set PYPI_TOKEN --body "$PYPI_TOKEN"

5. Add the Speakeasy GitHub Action workflow

Create .github/workflows/sdk-generation.yaml. Pin the action to a major version and set action: run-workflow with mode: pr so changes arrive as reviewable pull requests.

# .github/workflows/sdk-generation.yaml
name: Generate SDKs
on:
  push:
    branches: [main]
    paths:
      - "openapi.yaml"
      - ".speakeasy/**"
  workflow_dispatch: {}

jobs:
  generate:
    permissions:
      checks: write
      contents: write
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - uses: speakeasy-api/sdk-generation-action@v15
        with:
          action: run-workflow
          mode: pr
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}

6. Trigger regeneration on spec change

Commit a spec change and push. The action runs, and if the SDK output differs, it opens a PR.

git commit -am "feat(api): add /invoices endpoint"
git push origin main

Expected result in the Actions log:

Speakeasy SDK Generation
✓ Regenerated typescript-sdk (1 endpoint added)
✓ Regenerated python-sdk (1 endpoint added)
✓ Opened PR #42: chore: regenerate SDKs

Merging PR #42 with the release step enabled bumps the version, tags the commit, and publishes the SDKs.

Complete Working Example

A single repository setup: .speakeasy/workflow.yaml that defines the source and two targets with publishing, and the GitHub Action that runs it on spec change and publishes on merge.

# .speakeasy/workflow.yaml
workflowVersion: 1.0.0
speakeasyVersion: 1.517.3
sources:
  acme-api:
    inputs:
      - location: ./openapi.yaml
targets:
  typescript-sdk:
    target: typescript
    source: acme-api
    publish:
      npm:
        token: $NPM_TOKEN
  python-sdk:
    target: python
    source: acme-api
    publish:
      pypi:
        token: $PYPI_TOKEN
# .github/workflows/sdk-generation.yaml
name: Generate and Publish SDKs
on:
  push:
    branches: [main]
    paths:
      - "openapi.yaml"
      - ".speakeasy/**"
  workflow_dispatch: {}

jobs:
  generate:
    permissions:
      checks: write
      contents: write
      pull-requests: write
      statuses: write
    runs-on: ubuntu-latest
    steps:
      - name: Regenerate SDKs and open PR
        uses: speakeasy-api/sdk-generation-action@v15
        with:
          action: run-workflow
          mode: pr
          force: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}

  publish:
    needs: generate
    permissions:
      contents: write
      statuses: write
    runs-on: ubuntu-latest
    steps:
      - name: Publish on merged regeneration
        uses: speakeasy-api/sdk-generation-action@v15
        with:
          action: release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}

Pushing a spec change opens a regeneration PR; merging it runs the release action, which tags and publishes the new SDK versions.

Gotchas & Edge Cases

The action needs write permissions to open PRs and tag releases. The default GITHUB_TOKEN has read-only permissions on many repositories. Grant contents: write and pull-requests: write in the job’s permissions block, or the action fails with Resource not accessible by integration when it tries to push the regeneration branch.

A no-op run is correct behavior, not a failure. If the spec change does not alter the generated SDK (for example, a description-only tweak), Speakeasy detects no diff and exits without opening a PR or cutting a release. Do not add force: true to “fix” this — forcing regeneration creates empty version bumps and noisy releases.

mode: direct skips review. Setting mode: direct commits regenerated SDKs straight to the branch, which is convenient for trusted internal repos but removes the human checkpoint. For public SDKs keep mode: pr so a reviewer can catch breaking changes before they reach npm or PyPI.

FAQ

What is the difference between the action’s action and mode inputs?

The action input chooses the operation, with run-workflow executing the targets in .speakeasy/workflow.yaml. The mode input chooses how results are delivered, where pr opens a regeneration pull request for review and direct commits straight to the branch.

When does the Speakeasy action create a release?

When the generated SDK changes and the action is configured with a release step, Speakeasy bumps the version, tags the commit, and publishes per the publish block in workflow.yaml. A run that produces no SDK changes ends without opening a PR or cutting a release.

Do I need a Speakeasy API key in CI?

Yes. The action authenticates with a SPEAKEASY_API_KEY secret to pull your workspace configuration and generator entitlements. Store it as an encrypted GitHub Actions secret and pass it through the env block of the workflow job.