TryMellon
Navigation

API key rotation

Rotate an application's client_secret with a grace period for zero-downtime deploys.

API key rotation

Rotate an application’s client_secret from the dashboard or via API. The previous secret stays valid during a configurable grace period (default 15 minutes, max 60 minutes) so you can deploy the new secret without auth downtime.

When to rotate

  • Compromised secret — rotate immediately, then set grace_period_seconds: 0 once you’ve confirmed no legitimate clients are still using the old one.
  • Quarterly hygiene — schedule a rotation every 90 days as part of your security review.
  • After contractor offboarding — anyone who ever read the secret should no longer be able to use it.

Flow

sequenceDiagram
  participant U as User (dashboard)
  participant API as TryMellon API
  participant CI as Your CI/CD
  participant App as Your app

  U->>API: POST /v1/applications/:id/rotate-secret
  API-->>U: { new_client_secret, previous_secret_expires_at }
  U->>CI: Update secret in vault
  CI->>App: Deploy with new secret
  Note over App,API: Both secrets accepted during grace window
  API-->>App: Previous secret rejected after expires_at

Rotate from the dashboard

  1. Open Dashboard → Applications → [your app] → Settings.
  2. Section API keysRotate client secret.
  3. Confirm the warning modal.
  4. Copy the new secret immediately — it is shown once. The dashboard never displays it again.
  5. The previous secret expires at the timestamp shown (“previous expires in 14:53”). Deploy the new secret to all callers before the countdown hits zero.

Rotate from cURL

curl -X POST https://api.trymellonauth.com/v1/applications/$APP_ID/rotate-secret \
  -H "Authorization: Bearer $TENANT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rotation-$(date +%s)" \
  -d '{ "grace_period_seconds": 900 }'

Response (200):

{
  "ok": true,
  "data": {
    "application_id": "uuid",
    "new_client_secret": "tm_secret_…",
    "previous_secret_expires_at": "2026-04-15T15:14:00.000Z",
    "rotated_at": "2026-04-15T15:00:00.000Z"
  }
}

The Idempotency-Key header makes the call replay-safe — re-issuing the same request returns the cached response instead of generating a second secret.

CI/CD: zero-downtime rotation (GitHub Actions example)

name: Rotate TryMellon secret
on:
  workflow_dispatch:
  schedule:
    - cron: '0 9 1 */3 *'   # Quarterly, 1st day of month, 09:00 UTC

jobs:
  rotate:
    runs-on: ubuntu-latest
    steps:
      - name: Rotate secret
        id: rotate
        run: |
          response=$(curl -sS -X POST \
            "https://api.trymellonauth.com/v1/applications/${{ secrets.TM_APP_ID }}/rotate-secret" \
            -H "Authorization: Bearer ${{ secrets.TM_TENANT_TOKEN }}" \
            -H "Idempotency-Key: rotate-${{ github.run_id }}" \
            -d '{"grace_period_seconds": 1800}')
          echo "secret=$(echo "$response" | jq -r '.data.new_client_secret')" >> $GITHUB_OUTPUT

      - name: Push to vault (example: GitHub Secrets)
        env:
          GH_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          gh secret set TM_CLIENT_SECRET --body "${{ steps.rotate.outputs.secret }}"

      - name: Trigger production deploy
        run: gh workflow run deploy.yml

The 30-minute grace window gives the deploy plenty of time to roll out before the previous secret stops working.

Troubleshooting

SymptomLikely causeFix
403 SECRET_ROTATION_FORBIDDENCaller is not creator, tenant owner, or account owner.Use a token from a user with one of those roles.
401 after deployOld pods still picking up old secret from cache.Confirm rolling restart completed; old secret remains valid until previous_secret_expires_at.
Two rotations in a rowForgot to copy the secret first time.Wait for the previous-secret window to expire, then rotate again. There’s no “show secret again” — by design.

Audit trail

Every rotation emits the application.secret_rotated webhook event and is logged in the audit log (event = application.secret_rotated, metadata.rotated_by = userId). View it under Dashboard → Audit log.