Skip to main content

Publish your SDKs

Once you are satisfied with your SDK, you can distribute it to end users by publishing it to a package registry, like npm, PyPI, or RubyGems. After publishing, generated SDK code goes to a GitHub repo you own, where it's picked up by package registries.

The Stainless-managed release flow handles the versioning and changelogs expected in a quality SDK. You can even automate this process so changes to your OpenAPI spec trigger new releases.

Before you publish an SDK, we push the generated code in a staging repo under the stainless-sdks GitHub organization. When your SDK is published, we instead push the generated SDK code to a GitHub repo you own, called the production repo.

Install the Stainless GitHub app

If your Stainless organization does not already have the Stainless GitHub app installed, you'll need to install it first.

  1. In the Stainless dashboard, navigate to your project.
  2. Navigate to Release > Production repos.
  3. Follow the installation flow. When prompted, select which repos the Stainless GitHub app can access.
  4. Once repos have been selected and permissions have been reviewed, click Install & Authorize.
warning

By default All repositories is selected, but we recommend you only select the repos you plan to use for Stainless SDKs.

GitHub App authorization screen

Choose a production repo

After installing the Stainless GitHub app, you can then choose or create the production repo for each SDK.

We recommend choosing a name that meaningfully identifies your SDK. For example, <company-name>-python is a common choice for Python SDKs.

The repo can be public or private.

  1. In the Stainless dashboard, navigate to your project.
  2. Navigate to Release > Production repos.
  3. If the Stainless GitHub app is installed, you will see a list of languages for your project. For each SDK you want to publish, use the dropdown to choose or create its production repo.
  4. Optionally, configure which branch of the production repo to push code to, by selecting it from the dropdown next to the repo name.
info

You need a choose a different GitHub repo for each language: one for TypeScript, one for Python, and so on.

warning

The production repo for a Terraform provider must be in the form terraform-provider-<name>.

Connect package registries

Package registries make your packages readily available to your users. After a one-time setup, Stainless can publish code pushed to your production repo.

Node/TypeScript: npm

Get an access token
  1. Log in or sign up at npm.
  2. Select your profile picture on the top right to open a dropdown.
  3. Navigate to Access Tokens > Generate New Token.
  4. Choose a token version:
    • Granular Access Token, recommended if you’re publishing the SDK under a scope (for example, @<company-name>/sdk)
    • Classic Token, required if not publishing under a scope
  5. If creating a Classic Token, ensure you have selected Automation as the token type. Apart from that, the token settings you choose are up to you.
Add the token to your production repo
  1. In the production repo, navigate to Secrets and variables > Actions > New repository secret. The URL should look like https://github.com/<org>/<repo>/settings/secrets/actions/new.
  2. Add a new secret named NPM_TOKEN with your API token.
Choose a package name and update your Stainless config
  1. Choose an available package name. Suggested names are:

    • <company-name>
    • @<company-name>/sdk (This requires you have the right npm organization.)

    You can check if the name is available.

  2. Update the Stainless config with your package name and save.

    targets:
    node:
    package_name: <package-name>
    publish:
    npm: true

Deno: JSR

Create a JSR package
  1. Log in or sign up at JSR.
  2. Select Publish a package.
  3. Choose an appropriate scope and package name.
  4. Select Create.
(Option 1) Publish with GitHub OIDC
  1. In JSR, navigate to <package-name> > Settings > GitHub Repository.

  2. Link the production repo to the JSR package you created.

  3. Navigate to your <scope-name> > Settings > GitHub Actions security.

  4. Select Do not restrict publishing. This will let the Stainless GitHub App publish even though it is not a member of your scope.

  5. Update the Stainless config with your package name and save.

    targets:
    node:
    publish:
    jsr:
    package_name: @<scope-name>/<package-name>
(Option 2) Use access token to publish
  1. In JSR, navigate to Account > Tokens > Personal access tokens > Create new token.

  2. Navigate through wizard to get your token:

    1. Publish packages
    2. A development machine
    3. Create a token
    4. Create a token for the package name.
  3. In your production repo, navigate to Secrets and variables > Actions > New repository secret. The URL should look like https://github.com/<org>/<repo>/settings/secrets/actions/new.

  4. Add a new secret named JSR_TOKEN with your API token.

  5. Update the Stainless config with your package name and use_access_token, and save.

    node:
    package_name: <package-name>
    publish:
    npm: true
    jsr:
    package_name: @<scope-name>/<package-name>
    use_access_token: true

Python: PyPI

Get an API token
  1. Log in or sign up at PyPI.
  2. Select your profile picture on the top right to open a dropdown.
  3. Navigate to Access settings > API tokens > Add API token. You may have to verify your email address and set up 2FA if you haven't already done so.
Add the token to your production repo
  1. In your production repo, navigate to Secrets and variables > Actions > New repository secret. The URL should look like https://github.com/<org>/<repo>/settings/secrets/actions/new.
  2. Add a new secret named PYPI_TOKEN with your API token.
Choose a package name and update your Stainless config
  1. Choose an available package name. Suggested names are:

    • <company-name>
    • <company-name>-client

    You can check whether the name is available by testing the link at https://pypi.org/project/<package-name>.

  2. Update the Stainless config with your package name and save.

    targets:
    python:
    package_name: <package-name>
    publish:
    pypi: true

Go: GitHub

Go installs packages from source, so nothing other than the GitHub repository is required for setup. We automatically request updates from the Go package index which updates your godoc on a release.

Java/Kotlin: Sonatype Maven Central

We publish JVM packages to Sonatype. Sonatype has both a legacy (OSSRH) publishing flow and a new Central Portal publishing flow.

If you do not already publish to OSSRH, you will most likely have to use the new Central Portal publishing flow. If you are already publishing to OSSRH, you can continue to do so.

warning

Setting up Sonatype can be an involved process. We recommend you go through this process after getting your SDKs into a satisfactory state.

Update your Stainless config (Central Portal only)

For backwards compatibility, the default sonatype_platform is ossrh. If you are using the Central Portal, you need to update the Stainless config to portal:

targets:
java:
reverse_domain: com.example.api
publish:
maven:
sonatype_platform: portal
Add namespace (Central Portal only)
  1. Add a namespace on Sonatype Portal

    The namespace will be part of your package's name. It will be the inverse of the domain that you own. i.e. tld.domain.subdomain

  2. Copy the Verification Key

    You will be able to prove your ownership of the namespace by adding a TXT record of the verification key to your domain.

Prove ownership over your domain

Add a temporary TXT record to the DNS for your primary domain (the reverse of your groupId) to verify that the person registering this Java group actually controls the domain.

If using the Central Portal flow, the contents of the TXT record is the verification key provided by Sonatype.

If using the OSSRH flow, the contents of the TXT record is the ID of the JIRA ticket created in the previous step (for example, OSSRH-12345).

Generate user token (Central Portal only)

From the Sonatype dashboard, generate a user token XML file.

Extract <username>…</username> and <password>…</password> from the XML file and store them somewhere secure.

<server>
<id>${server}</id>
<username>//P7KNy5</username>
<password>+8sEiCiUrSoODM/S4yFhiKqUWJzS1lDbcac5I7hVzPS4</password>
</server>
Generate a PGP key
  1. Install GnuPG.

  2. In a terminal, run gpg --gen-key. Enter your name, email, and a passphrase for the keypair.

  3. Run gpg --list-keys --keyid-format short:

    % gpg --list-keys --keyid-format short
    [keyboxd]
    ---------
    pub ed25519/7FAD9FAA 2023-08-02 [SC] [expires: 2026-08-01]
    418D41921938A753CAAE57985114AD037FAD9FAA
    uid [ultimate] Your Organization <you@yourorg.com>
    sub cv25519/07EB38A7 2023-08-02 [E] [expires: 2026-08-01]
  4. Export the key in ASCII-armored format. In the above example, the short key ID is 7FAD9FAA.

    $ gpg --export-secret-keys --armor 7FAD9FAA
    -----BEGIN PGP PRIVATE KEY BLOCK-----

    lIYEZMpnEhYJKwYBBAHaRw8BAQdA1FQMKz+wwliKNdLehegZP0QiaKrZJqADNyVn
    VCUzIrD+BwMCwZrqVbVPfSr8NwxEh3M6kWtMGmnLMOk/NWVe7dtCxDxo37l/Ncxj
    Mm9EZiH6WXwoXXq20nOW354oNOVz/UPvDU+oaRDDUM9SYs392i69WLQjWW91ciBP
    cmdhbml6YXRpb24gPHlvdUB5b3Vyb3JnLmNvbT6ImQQTFgoAQRYhBEGNQZIZOKdT
    yq5XmFEUrQN/rZ+qBQJkymcSAhsDBQkFo5qABQsJCAcCAiICBhUKCQgLAgQWAgMB
    Ah4HAheAAAoJEFEUrQN/rZ+qgtsBAMCBuTqYEJljxStRO7SsMLWOc47CIIXD0Yid
    CbySBX0ZAP9DXuuGVYbHFONvHxNKszu2hY9A1BbRuNjeGeWuVOw9ApyLBGTKZxIS
    CisGAQQBl1UBBQEBB0AFxkZ+ZdEN7Epwri/w5ETAf+MOqdwAP2sS6TccSjEiXQMB
    CAf+BwMCtKG1TBUSC/z8tn761I5j+ifVIuMqdQPYIhZtjvIyC+NyrBi0j1ZtUG4A
    DAeDKNyM63uyb7omOH8+Lu0J71SGhVnZWszUOf3rrT/TA5MktYh+BBgWCgAmFiEE
    QY1Bkhk4p1PKrleYURStA3+tn6oFAmTKZxICGwwFCQWjmoAACgkQURStA3+tn6qa
    XAD/W2ucVURngmCUiUtdjQAZz36yQYPQmBhcdabZMyXKHz0A/itwYkuRbD2mp4p/
    xJk/QLXs/2/xBA6s0ROVVspy6MEA
    =p/g/
    -----END PGP PRIVATE KEY BLOCK-----
  5. Publish your PGP public key.

    $ gpg --keyserver keyserver.ubuntu.com --send-keys 7FAD9FAA

    If you get the following error:

    gpg: sending key XXXXXX to hkp://keyserver.ubuntu.com
    gpg: keyserver send failed: No route to host

    then your computer might be trying to use IPv6 and failing. Run the following to get keyserver.ubuntu.com's IPv4 addresses:

    $ host keyserver.ubuntu.com
    keyserver.ubuntu.com has address XXX.XXX.XXX.XX
    keyserver.ubuntu.com has address XXX.XXX.XXX.XX
    keyserver.ubuntu.com has IPv6 address XXXX:XX:XXXX:XXXX::XXX
    keyserver.ubuntu.com has IPv6 address XXXX:XX:XXXX:XXXX::XXX

    Then rerun the gpg command with one of the IPv4 addresses:

    $ gpg --keyserver XXX.XXX.XXX.XX --send-keys 7FAD9FAA
Add secrets to your production repo
  1. In the production repo, navigate to Secrets and variables > Actions > New repository secret. The URL should look like https://github.com/<org>/<repo>/settings/secrets/actions/new.
  2. Add the following secrets:
  • {ORG_NAME}_SONATYPE_GPG_SIGNING_KEY: your PGP private key. It should be the entire output of gpg --export-secret-keys --armor <keyid>.

  • {ORG_NAME}_SONATYPE_GPG_SIGNING_PASSWORD: the passphrase you entered when creating the keypair.

  • {ORG_NAME}_SONATYPE_USERNAME: username for your Sonatype account.

  • {ORG_NAME}_SONATYPE_PASSWORD: password for your Sonatype account (OSSRH) or your Sonatype user token (Central Portal).

Ruby: RubyGems

Get an API token
  1. Log in or sign up at RubyGems.
  2. Select your profile picture on the top right to open a dropdown.
  3. Navigate to Settings > API keys.
  4. Create a new API key with the "Push rubygems" privilege. Make sure MFA is disabled.
Add the token to your production repo
  1. In your production repo, navigate to Secrets and variables > Actions > New repository secret. The URL should look like https://github.com/<org>/<repo>/settings/secrets/actions/new.
  2. Add a new secret named GEM_HOST_API_KEY with your API token.
Choose a package name and update your Stainless config
  1. Choose an available package name. Suggested names are:

    • <company-name>
    • <company-name>-client

    You can check whether the name is available by testing the link at https://rubygems.org/gems/<package-name>.

  2. Update the Stainless config with your package name and save.

    targets:
    ruby:
    gem_name: <package-name>
    publish:
    rubygems: true

Terraform: Terraform Registry

The following steps allow you to publish your provider to the registry. If you have more custom requirements, you can also refer to the official HashiCorp publishing documentation.

Connect Terraform Registry to your production repo
  1. Log in or sign up at the Terraform Registry.
  2. Select Publish Provider.
  3. Follow the instructions to install the Hashicorp GitHub App.
  4. In the provider publishing page, select the connected user / organization.
  5. Select your production repo and follow the instructions to install the webhooks required to publish the provider.
Generate a PGP key
  1. Install GnuPG.

  2. In a terminal, run gpg --gen-key. Enter your name, email, and a passphrase for the keypair.

  3. Run gpg --list-keys --keyid-format short:

    % gpg --list-keys --keyid-format short
    [keyboxd]
    ---------
    pub ed25519/7FAD9FAA 2023-08-02 [SC] [expires: 2026-08-01]
    418D41921938A753CAAE57985114AD037FAD9FAA
    uid [ultimate] Your Organization <you@yourorg.com>
    sub cv25519/07EB38A7 2023-08-02 [E] [expires: 2026-08-01]
  4. Export the key in ASCII-armored format. In the above example, the short key ID is 7FAD9FAA.

    $ gpg --export-secret-keys --armor 7FAD9FAA
    -----BEGIN PGP PRIVATE KEY BLOCK-----

    lIYEZMpnEhYJKwYBBAHaRw8BAQdA1FQMKz+wwliKNdLehegZP0QiaKrZJqADNyVn
    VCUzIrD+BwMCwZrqVbVPfSr8NwxEh3M6kWtMGmnLMOk/NWVe7dtCxDxo37l/Ncxj
    Mm9EZiH6WXwoXXq20nOW354oNOVz/UPvDU+oaRDDUM9SYs392i69WLQjWW91ciBP
    cmdhbml6YXRpb24gPHlvdUB5b3Vyb3JnLmNvbT6ImQQTFgoAQRYhBEGNQZIZOKdT
    yq5XmFEUrQN/rZ+qBQJkymcSAhsDBQkFo5qABQsJCAcCAiICBhUKCQgLAgQWAgMB
    Ah4HAheAAAoJEFEUrQN/rZ+qgtsBAMCBuTqYEJljxStRO7SsMLWOc47CIIXD0Yid
    CbySBX0ZAP9DXuuGVYbHFONvHxNKszu2hY9A1BbRuNjeGeWuVOw9ApyLBGTKZxIS
    CisGAQQBl1UBBQEBB0AFxkZ+ZdEN7Epwri/w5ETAf+MOqdwAP2sS6TccSjEiXQMB
    CAf+BwMCtKG1TBUSC/z8tn761I5j+ifVIuMqdQPYIhZtjvIyC+NyrBi0j1ZtUG4A
    DAeDKNyM63uyb7omOH8+Lu0J71SGhVnZWszUOf3rrT/TA5MktYh+BBgWCgAmFiEE
    QY1Bkhk4p1PKrleYURStA3+tn6oFAmTKZxICGwwFCQWjmoAACgkQURStA3+tn6qa
    XAD/W2ucVURngmCUiUtdjQAZz36yQYPQmBhcdabZMyXKHz0A/itwYkuRbD2mp4p/
    xJk/QLXs/2/xBA6s0ROVVspy6MEA
    =p/g/
    -----END PGP PRIVATE KEY BLOCK-----
  5. Publish your PGP public key.

    $ gpg --keyserver keyserver.ubuntu.com --send-keys 7FAD9FAA

    If you get the following error:

    gpg: sending key XXXXXX to hkp://keyserver.ubuntu.com
    gpg: keyserver send failed: No route to host

    then your computer might be trying to use IPv6 and failing. Run the following to get keyserver.ubuntu.com's IPv4 addresses:

    $ host keyserver.ubuntu.com
    keyserver.ubuntu.com has address XXX.XXX.XXX.XX
    keyserver.ubuntu.com has address XXX.XXX.XXX.XX
    keyserver.ubuntu.com has IPv6 address XXXX:XX:XXXX:XXXX::XXX
    keyserver.ubuntu.com has IPv6 address XXXX:XX:XXXX:XXXX::XXX

    Then rerun the gpg command with one of the IPv4 addresses:

    $ gpg --keyserver XXX.XXX.XXX.XX --send-keys 7FAD9FAA
Add secrets to your production repo
  1. In the production repo, navigate to Secrets and variables > Actions > New repository secret. The URL should look like https://github.com/<org>/<repo>/settings/secrets/actions/new.
  2. Add the following secrets:
  • GPG_SIGNING_KEY: your PGP private key. It should be the entire output of gpg --export-secret-keys --armor <keyid>.

  • GPG_SIGNING_PASSWORD: the passphrase you entered when creating the keypair.

Update your Stainless config
  1. Update the Stainless config and save.

    targets:
    terraform:
    publish:
    hashicorp_registry: true
Merge your release PR and see it appear in the Registry
  • Merge your release PR when you're ready. This should then create a tag, use goreleaser to build artifacts, and attach it to the release. Hashicorp will then import the release into their registry. You might need to wait a few minutes for it to show up on your provider page.

Versioning and releases

Whenever we generate a new change for your SDK, we create and merge a PR into the next branch in your production repository. Each commit should describe the related change using the Conventional Commits format.

Public Repository
📓 acme/acme-node
Public Repository...
main
main
release 0.1.0
release 0.1.0
feat(api): add get /user endpoint
feat(api): add get /user endpoint
feat(api): add post /user endpoint
feat(api): add post /user endpoint
release 0.2.0
release 0.2.0
next
next
Release Pull Request
Release Pull Request

A Release PR is then created against the main branch, which collects individual changes until you are ready to release them. The PR waits for a code owner's review and merges automatically upon approval.

The Release PR aggregates changes due to both your API spec changes and changes to Stainless' codegen engine. Each granular change is added as a separate commit, which you can review individually.

Your first release will be Release 0.1.0-alpha.1, meaning that the first release published to your package manager will have an alpha tag.

Stainless will increment the version used in each subsequent release PR based on the prior one. We use the semantic information of the commits in the release to find the next available version. We highly recommend using Conventional Commits to ensure versioning is correct and automatic:

  • Breaking changes increment the major version number (if and only if the version is already at least 1.0.0)
  • Features increment the minor version number
  • Fixes increment the patch number

You can override our suggested version by modifying the PR title. Doing so will propagate your provided version (including any alpha or beta tags) to the release in Github and your package manager. We recommend:

  • Staying in the alpha or beta stage while your SDKs still have diagnostic errors
  • Staying in the 0.0.x range while your SDKs still have diagnostic warnings
  • Staying in the 0.x.y range while your SDKs still have unacknowledged diagnostic notes
  • Moving to 1.0.0 once your SDK has none of the above

We use semver-valid version numbers for releases.

Development and release flow

Stainless pushes code to various GitHub repos:

  1. Stainless config repo
  2. Stainless SDK repos
  3. Your production repos

Release flow

Release flow

Stainless uses a few branches in your production repository to manage the release process:

  • main/master: The target branch of your repository. This defaults to main but can be configured per language using production_repo.
  • next: This branch is used to collect changes for the next release. Stainless will create Release PRs based on the changes in this branch.
  • generated: The branch where Stainless pushes generated code from the Studio. This branch represents the latest generated code before any custom code changes are applied.
  • release-please--*: Temporary release branches created and managed by our GitHub App. These branches are based on next and are used to create release PRs.