Rotate API Key
Rotate API keys every 90 days or immediately if compromised.
Why Rotate
- Expiration: TTL reached (90 days default)
- Compromise: Key leaked in logs, GitHub, etc.
- Revocation: Service no longer needs access
- Compliance: Security policy requires rotation
Process
1. Create New Key
velocity api-key create \ --name ci-deploy-2026-06 \ --ttl 90d \ --scope region=west,store_ids=10:20:30Output:
vel_ci-deploy-2026-06_abc123def456xyz...SAVE THIS NOW — you will not see it again.Critical: Copy the plaintext immediately. The CLI never stores or retrieves it.
2. Store in Secret Manager
Add the new key to your secret manager (not Git):
# GitHub Secretsgh secret set VELOCITY_API_KEY -b "vel_ci-deploy-2026-06_abc123def456xyz..."
# AWS Secrets Manageraws secretsmanager put-secret-value \ --secret-id velocity-api-key \ --secret-string '{"key":"vel_ci-deploy-2026-06_abc123def456xyz..."}'
# HashiCorp Vaultvault kv put secret/velocity/api-key key="vel_ci-deploy-2026-06_abc123def456xyz..."3. Update Deployments
Update all places using the old key:
GitHub Actions workflow:
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy env: VELOCITY_API_KEY: ${{ secrets.VELOCITY_API_KEY }} run: | curl -H "X-API-Key: $VELOCITY_API_KEY" \ https://api.velocity.acme.com/api/acme/supply-chain/...Kubernetes CronJob:
apiVersion: batch/v1kind: CronJobmetadata: name: velocity-syncspec: schedule: "0 2 * * *" jobTemplate: spec: template: spec: serviceAccountName: velocity-sync containers: - name: sync image: curlimages/curl:latest env: - name: VELOCITY_API_KEY valueFrom: secretKeyRef: name: velocity-api-key key: key command: ["/bin/sh"] args: - -c - | curl -H "X-Api-Key: $VELOCITY_API_KEY" \ https://api.velocity.acme.com/api/acme/...Docker environment variable:
docker run \ -e VELOCITY_API_KEY="vel_ci-deploy-2026-06_abc123def456xyz..." \ my-service:latest4. Verify New Key Works
Test the new key before revoking the old one:
# Test new keycurl -H "X-API-Key: vel_ci-deploy-2026-06_abc123def456xyz..." \ https://api.velocity.acme.com/api/acme/supply-chain/procurement/purchase-order/v1
# Expected: 200 OK with data5. Revoke Old Key
Once verified, revoke the old key:
velocity api-key revoke --name ci-deploy-2025-12
# Verify revocationcurl -H "X-API-Key: vel_ci-deploy-2025-12_oldkey..." \ https://api.velocity.acme.com/api/acme/supply-chain/procurement/purchase-order/v1
# Expected: 401 Unauthorized (key revoked)6. Update Documentation
Update team wiki with new key expiration date:
## API Key Rotation Schedule
| Name | Expires | Last Rotated ||--------------------|--------------|--------------|| ci-deploy-2026-06 | 2026-09-18 | 2026-06-19 || data-sync-2026-06 | 2026-09-15 | 2026-06-15 |Automation (Optional)
GitHub Actions: Auto-Rotate on Schedule
name: Rotate API Keyon: schedule: - cron: '0 0 1 * *' # First day of month
jobs: rotate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Create new key id: new_key run: | KEY=$(velocity api-key create --name ci-deploy-$(date +%Y-%m) --ttl 90d) echo "key=$KEY" >> $GITHUB_OUTPUT
- name: Update secret env: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | gh secret set VELOCITY_API_KEY -b "${{ steps.new_key.outputs.key }}"
- name: List old keys run: | velocity api-key list | tail -5
- name: Notify team run: | curl -X POST ${{ secrets.SLACK_WEBHOOK }} \ -d '{"text":"API key rotated. Old key expires in 90 days."}'Kubernetes CronJob: Auto-Rotate
apiVersion: batch/v1kind: CronJobmetadata: name: velocity-api-key-rotate namespace: velocity-systemspec: schedule: "0 0 1 * *" # First day of month jobTemplate: spec: template: spec: serviceAccountName: velocity-admin containers: - name: rotate image: curlimages/curl:latest command: - /bin/sh - -c - | # Assume velocity CLI is installed NEW_KEY=$(velocity api-key create --name ci-deploy-$(date +%Y-%m) --ttl 90d) kubectl patch secret velocity-api-key -p "{\"data\":{\"key\":\"$(echo -n $NEW_KEY | base64)\"}}" restartPolicy: OnFailureEmergency Revocation
If key is compromised immediately:
velocity api-key revoke --name ci-deploy-2026-06
# Verify revocation (within seconds)curl -H "X-API-Key: vel_ci-deploy-2026-06_abc123def456xyz..." \ https://api.velocity.acme.com/api/acme/supply-chain/procurement/purchase-order/v1
# Expected: 401 UnauthorizedNo key regeneration needed; just create a new one.
Monitoring
Alert on key usage anomalies:
# Check last-used timestampvelocity api-key list | grep ci-deploy-2026-06
# Alert if key hasn't been used in 30 days (may be forgotten)# Alert if key is used from unexpected IP (possible theft)Checklist
- Created new API key
- Stored in secret manager (not plaintext in Git)
- Updated all deployments/scripts
- Tested new key works (200 OK response)
- Revoked old key
- Updated team documentation
- Verified revocation (401 on old key)
- Notified team of rotation date
- Scheduled next rotation (90 days ahead)