How to rotate API keys safely in CI/CD without leaking them
Hard-coded, never-rotated API keys are one of the most common ways secrets leak from a pipeline. This guide walks through generating, rotating, revoking, and auditing keys from the command line with APIAuth — keeping plaintext out of your repo, your logs, and your CI config.
Last updated: June 2026 · ~6 min read
Why rotation matters (and why it usually doesn't happen)
Most teams know they should rotate credentials, but rotation is manual, error-prone, and easy to skip. The failure modes are familiar:
- Keys committed to git — a single
git pushcan expose a credential to everyone with repo access, and history keeps it forever. - Keys that never expire — a credential leaked two years ago may still be valid today.
- No clean rotation path — when there's no tooling, rotating means hand-editing secrets across environments and hoping nothing breaks.
- No audit trail — nobody knows which keys are expired, expiring, or already revoked.
APIAuth is a small CLI that addresses each of these with an encrypted local keystore (AES-256-GCM), expiry-aware key generation, one-command rotation and revocation, and export formats that hand secrets to CI without writing them into your config files.
Step 0 — Install APIAuth
APIAuth is published to the project's own package index (it is not on public PyPI yet), so point pip at the index — or install straight from source:
# From the project package index (recommended)
pip install --index-url https://coding-dev-tools.github.io/pypi-index/simple/ apiauth
# Or directly from source (always tracks the latest code)
pip install git+https://github.com/Coding-Dev-Tools/apiauth.git
Requires Python 3.10+ — pure Python, no compiled wheels.
Step 1 — Generate a key with an expiry
Expiry is your safety net: even if rotation slips, the key dies on schedule. Scope each key to a service so you can rotate one integration without touching the rest.
apiauth generate api-key --name "Deploy Token" --service "api-gateway" --expiry-days 90
The plaintext value is printed once, at creation time. After that, only a hash is kept — APIAuth never stores the plaintext, so it can't leak it back.
Step 2 — Rotate on a schedule
Rotation issues a fresh value and hashes out the old one, so the previous key stops verifying:
apiauth list # find the key id + expiry status
apiauth rotate <key-id> # issue a new value, retire the old
Run this on a cadence (e.g. every 90 days, matching the expiry window). Pair it with your secret store's update step so the new value propagates to running services.
Step 3 — Revoke instantly if a key leaks
If a credential is exposed, you don't want to wait for rotation — kill it now:
apiauth revoke <key-id>
Revocation is immediate; apiauth verify will reject the key from that point on.
Step 4 — Hand keys to CI without writing them to config
The leak-prone move is pasting secrets into a workflow file or an unencrypted .env committed to the repo. Instead, export at runtime into the CI environment. APIAuth has a dedicated GitHub Actions format that writes to $GITHUB_ENV:
# In a GitHub Actions step
apiauth export --format github-actions --service "api-gateway" >> "$GITHUB_ENV"
# Other targets
apiauth export --format env --service production # export KEY=value
apiauth export --format dotenv # .env style, no export prefix
apiauth export --format json # programmatic consumption
Step 5 — Fail the build on expired keys
Make stale credentials a hard error instead of a surprise outage. Audit the keystore as a pipeline gate:
apiauth audit --exit-on-expired
A non-zero exit stops the deploy before a dead key reaches production. apiauth stats gives a quick keystore overview for dashboards or scheduled reports.
generate, verify, and env-format export, with up to 5 keys. The CI workflow above — the non-env export formats (including github-actions), audit, stats, JWT custom claims, and unlimited keys — is part of the Individual plan ($12/mo). We'd rather tell you that up front than have a command fail in your pipeline.
What you actually get
| Capability | Free | Individual ($12/mo) |
|---|---|---|
| generate / verify / revoke / rotate | ✓ | ✓ |
| Encrypted keystore (AES-256-GCM) | ✓ | ✓ |
| Number of keys | 5 | Unlimited |
| Export formats | env only | env / dotenv / github-actions / json |
| JWT with custom claims | — | ✓ |
audit & stats | — | ✓ |
Source: the APIAuth README pricing & per-tier feature table. A Suite plan ($49/mo) covers all 11 Revenue Holdings CLI tools; Team ($79/mo) adds a shared keystore. Annual billing saves ~20%.
Stop hand-rotating secrets in your pipeline.
APIAuth is MIT-licensed and works fully offline — no telemetry, no phone-home.
Get APIAuth on GitHub Also: OpenAPI mock server guide →FAQ
Where does APIAuth store my keys?
In an encrypted keystore under ~/.apiauth/, using AES-256-GCM. The master key (~/.apiauth/master.key) never leaves your machine, keys are stored as hashes, and plaintext is displayed only once at creation.
Does rotating a key invalidate the old one?
Yes — apiauth rotate issues a new value and hashes out the previous one, so the old key stops verifying. Use apiauth revoke to kill a key immediately without issuing a replacement.
Can I use it in GitHub Actions / GitLab CI?
Yes. apiauth export --format github-actions writes to $GITHUB_ENV; env and dotenv formats work for any shell-based pipeline. (Non-env formats are an Individual-plan feature.)
Is it free?
The CLI is free for individuals and open source (generate/verify/env-export, 5 keys). Unlimited keys, all export formats, JWT claims, and audit/stats are on the Individual plan at $12/mo.
APIAuth is part of Revenue Holdings — a suite of developer CLI tools. See also Envault for syncing and rotating .env files across environments.