PyPI CI/CD credentials: long-lived API token or Trusted Publishing
Choose between a long-lived PyPI API token and Trusted Publishing (OIDC) for CI/CD package uploads, with trade-offs, a GitHub Actions example, and validation steps.
08 Aug 2025, 19:27 UTC

The decision: static token or OIDC publisher
Your CI pipeline must upload a new package version to PyPI, and it needs credentials to authenticate the upload. The useful takeaway is to prefer Trusted Publishing (OIDC) when your CI provider supports it. Trusted Publishing replaces a long-lived PyPI API token with a short-lived token issued by the CI provider for each release job. Keep a long-lived token only for providers or environments that cannot obtain an OIDC token for pypi.org.
PyPI is the public Python Package Index. An API token is a long-lived credential that clients such as twine send with upload requests. Trusted Publishing is a PyPI feature that accepts OpenID Connect (OIDC) tokens from supported CI providers instead of a static password. OIDC is an identity layer that issues short-lived tokens proving where a CI job originated.
Constraints that narrow the choice
- The CI provider must issue an OIDC token with the audience
pypi.org. - A PyPI project owner or maintainer must create a trusted publisher record for the project.
- The publisher record must match the repository owner, repository name, workflow filename, and target environment exactly. PyPI does not use branch name as a publisher matching field, although the CI provider's OIDC token may include the Git ref as a separate claim.
- The upload client must support OIDC. Verify your installed twine version supports sending the CI OIDC token automatically by running
twine --versionand checking current twine and PyPI documentation.
Supported options compared
| Option | Credential lifetime | Where the secret lives | Scope | Rotation |
|---|---|---|---|---|
| Long-lived PyPI API token | Valid until manually revoked | CI secret store, such as repository secrets | Project or account scoped | Manual |
| Trusted Publishing (OIDC) | Short-lived; issued per CI job | Not stored; issued by the CI provider for pypi.org | Bound to repository, workflow filename, and environment | Automatic per job |
Trade-offs
Blast radius. If a long-lived token is exposed, every project the token can access is at risk. A compromised OIDC token is valid only for the duration of a job and under the conditions bound to the publisher, though a compromised CI account could still trigger jobs.
Rotation overhead. Long-lived tokens require manual rotation and secure storage. Trusted Publishing removes the stored secret because the CI provider issues a token for each job.
Setup complexity. A token is simpler to configure initially. Trusted Publishing requires creating a publisher record in PyPI and granting the workflow permission to request an OIDC token.
Failure mode. Removing or editing a trusted publisher can break existing pipelines without warning. Twine does not cache tokens to cause silent success; a missing or misconfigured credential fails visibly.
Two-factor authentication. Trusted Publishing works with PyPI two-factor authentication. It is not a reason to keep a token-based fallback.
Implementation: publish from GitHub Actions with Trusted Publishing
Assumptions: you publish from GitHub Actions, build with python -m build, and upload with twine. A PyPI owner or maintainer manages the project settings. Adjust Python and action versions for your environment.
1. Add the trusted publisher in PyPI
Open the PyPI project page, go to Trusted publishers, and add a publisher. Match these values exactly:
- Repository owner
- Repository name
- Workflow filename, for example
.github/workflows/release.yml - Target environment, for example
pypi-release
Verify these values in the live PyPI UI. Do not rely on memory or outdated screenshots.
2. Configure the workflow
name: Release to PyPI
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
environment: pypi-release
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install build tools
run: python -m pip install --upgrade build twine
- name: Build distribution archives
run: python -m build
- name: Check distribution metadata
run: python -m twine check dist/*
- name: Publish to PyPI
run: python -m twine upload dist/*
Where to run: the publish job runs on GitHub-hosted runners. python -m twine check dist/* can also run locally before pushing changes.
Required permissions: the workflow needs id-token: write so the runner can request an OIDC token. The PyPI account needs owner or maintainer rights to create or edit trusted publishers.
Placeholders: replace pypi-release with your environment name, .github/workflows/release.yml with your actual workflow path, and "3.x" with your supported Python version.
Expected checks: twine check dist/* exits with status 0; the PyPI project release history shows the new version after the workflow runs; job logs contain no PyPI API token.
Risks: publishing from the wrong release can ship an unintended version. A mismatched publisher configuration causes authentication failure without exposing a token.
With Trusted Publishing, the upload step omits a static password and relies on the OIDC token exposed by the CI environment. Do not hardcode a password or token in the workflow.
Validate before you remove the token fallback
- Open PyPI > Trusted publishers and confirm the publisher matches the repository owner, repository name, workflow filename, and target environment.
- Run the release workflow and check the PyPI release history for the new version.
- Inspect the job logs and confirm no PyPI API token appears.
- Optionally revoke a test trusted publisher, rerun the job, and confirm publication fails. This proves the pipeline depends on OIDC rather than a cached token.
Revoking or editing a publisher changes state. If the test breaks publishing, rollback by re-adding the trusted publisher in PyPI with the same repository, workflow filename, and environment values.
Limitations and verification
- Trusted Publishing behavior depends on the CI provider's OIDC implementation and PyPI's current publisher UI; verify against live project settings rather than memory.
- A project can have multiple trusted publishers. Removing or editing one can break existing pipelines without warning.
- Confirm your twine version supports OIDC uploads (
twine --version); version-specific behavior should be checked against current twine and PyPI documentation before removing token-based publishing. - Publisher details such as repository owner, repository name, workflow filename, and environment are not secrets and may appear in workflow files. Do not commit API tokens to version control.
- This guide describes well-established PyPI behavior, but the exact publisher UI and twine integration should be verified against your installed versions before removing token-based publishing.
Start with Trusted Publishing for new CI/CD pipelines. Remove the token fallback only after the OIDC path succeeds repeatedly in your release process.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.