diff --git a/docs/apikeys.mdx b/docs/apikeys.mdx
index 7e44d2851a1..e7612c06a95 100644
--- a/docs/apikeys.mdx
+++ b/docs/apikeys.mdx
@@ -119,7 +119,7 @@ The key must belong to the target environment. Use a Production key with `--env
Set an expiration date when creating a key if the integration only needs temporary access. An expired key stops authenticating automatically.
-Revoking a key takes effect immediately and cannot be reversed. Requests using the key fail, and the key can no longer create public access tokens. Create a replacement before revoking a key when you need to rotate it without interrupting the integration.
+Revoking a key takes effect immediately and cannot be reversed. Requests using the key fail, and the key can no longer create public access tokens. Create a replacement before revoking a key when you need to rotate it without interrupting the integration. See [Rotate API keys without downtime](/rotate-api-keys).
Removing a team member does not revoke keys they created. Review and revoke their keys separately when their access changes.
diff --git a/docs/docs.json b/docs/docs.json
index 454ecc49018..922b3cec10c 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -47,6 +47,7 @@
"pages": ["runs/list-best-practices"]
},
"apikeys",
+ "rotate-api-keys",
"oidc"
]
},
diff --git a/docs/rotate-api-keys.mdx b/docs/rotate-api-keys.mdx
new file mode 100644
index 00000000000..a0c090d141f
--- /dev/null
+++ b/docs/rotate-api-keys.mdx
@@ -0,0 +1,111 @@
+---
+title: "Rotate API keys without downtime"
+sidebarTitle: "Rotate API keys"
+description: "Replace a Trigger.dev API key while both the old and new keys work, so your backend, CI, and deployed tasks never fail to authenticate."
+---
+
+**Rotate an API key without failed requests by keeping the previous key valid until everything uses the new one.** You issue the new key, roll it out to every service that calls Trigger.dev, confirm nothing still uses the old key, and only then revoke it.
+
+How you get that overlap depends on the key:
+
+| Key | How the overlap works |
+| --- | --- |
+| [Individual API key](#rotate-an-individual-api-key) | Create a second key, roll it out, then revoke the first. Both keys work until you revoke. |
+| [Root key](#rotate-the-root-key) | Regenerate it and keep the previous key working for 24 hours while you roll out the new one. |
+
+If a key may have leaked, follow [Rotate a key that may have leaked](#rotate-a-key-that-may-have-leaked) instead.
+
+## Find everywhere the key is used
+
+List every place that holds the key before you rotate it. A service you miss keeps sending the old key and fails once it's revoked.
+
+- **Backend environment variables**: `TRIGGER_SECRET_KEY` wherever you trigger tasks or call the API, for example Vercel, Railway, Fly, or your own servers.
+- **Secret managers**: AWS Secrets Manager, Doppler, 1Password, or similar.
+- **CI**: `TRIGGER_ACCESS_TOKEN` in workflows that run `trigger deploy`, for example [GitHub Actions](/github-actions).
+- **Code that configures the SDK directly**: any `configure({ secretKey })` call that reads the key from a different variable.
+
+Deployed tasks don't need to be on this list. We inject `TRIGGER_SECRET_KEY` into every run, so runs that start after you rotate the root key receive the new key without a redeploy.
+
+## Rotate an individual API key
+
+Individual API keys rotate with no time limit, because the old key keeps working until you revoke it.
+
+
+
+ Open [**API keys**](https://cloud.trigger.dev/_/apikeys) for the same environment and click **New API key**. Give it the same access preset and task selection as the key it replaces, and a name that tells the two apart, such as `checkout-service-2026-11`. Copy the secret, because it's shown only once.
+
+
+ Update the key in every place you listed, then redeploy or restart each service so it reads the new value. Most hosts, including Vercel, only apply a changed environment variable on the next deploy.
+
+
+ On the API keys page, check the **Last used** column. The new key's time should be recent, and the old key's should stop moving once every service has switched.
+
+
+ Click **Revoke…** on the old key. Revoking takes effect immediately and can't be undone.
+
+
+
+## Rotate the root key
+
+The root key is the key marked **Root** on the API keys page. Regenerating it can keep the previous root key valid for 24 hours, which is the window you have to roll out the new one.
+
+
+
+ On the root key's row, open the menu and click **Regenerate…**. Choose **Keep the previous key working for 24 hours**, which is the default, enter the confirmation text, and click **Regenerate**.
+
+ If the project uses the [Vercel integration](/vercel-integration), leave **Also update TRIGGER_SECRET_KEY in Vercel** checked to write the new key to Vercel for you. You still need to redeploy on Vercel for it to take effect.
+
+
+ Copy the new root key and update `TRIGGER_SECRET_KEY`, and `TRIGGER_ACCESS_TOKEN` if CI deploys with the root key, everywhere you listed. Redeploy or restart each service.
+
+
+ The previous root key stops working 24 hours after you regenerate. Any service still using it fails to authenticate after that, so complete the rollout inside the window.
+
+
+
+
+ Regenerating again doesn't extend the window. Each previous root key expires 24 hours after the
+ regeneration that replaced it, and choosing **Revoke the previous key immediately** ends every
+ window that is still open.
+
+
+## Rotate a key that may have leaked
+
+A leaked key should stop working as soon as possible, but revoking it before your services have the new key causes failed requests. Choose the order based on which key leaked.
+
+**An individual API key leaked.** Follow [Rotate an individual API key](#rotate-an-individual-api-key) and move through the steps quickly. The old key is valid only for the minutes it takes to roll out the replacement.
+
+**The root key leaked.** Regenerating with a 24-hour overlap leaves the leaked key valid for up to a day. Revoking it immediately breaks every service that still uses it. To avoid both, move your services off the root key first:
+
+
+
+ Click **New API key** and create a key with the access each service needs. Free and Hobby plans use the **No restrictions** preset.
+
+
+ Set `TRIGGER_SECRET_KEY` (and `TRIGGER_ACCESS_TOKEN` in CI) to the new individual key and redeploy each service. Check **Last used** on the new key to confirm traffic has moved.
+
+
+ Open **Regenerate…** on the root key and choose **Revoke the previous key immediately**. The leaked key and any older root key in its 24-hour window stop working now. Your services keep running because they no longer use the root key, and new task runs receive the new root key automatically.
+
+
+
+If you can't update your services quickly, regenerate the root key with **Revoke the previous key immediately** right away and accept that services still using it fail until you deploy the new key.
+
+A leaked key may also mean the credentials stored in your [environment variables](/deploy-environment-variables) were exposed. Rotate those with their providers and update them in Trigger.dev.
+
+## Verify the rotation
+
+- The new key's **Last used** time updates as your services make requests.
+- Triggering a task from each service succeeds and creates a run in the dashboard.
+- After the old key is revoked or its 24-hour window ends, no service logs authentication errors from Trigger.dev.
+
+## Next steps
+
+
+
+ Create keys, choose access presets, and set expiration dates.
+
+
+ Update the secrets your deployed tasks use.
+
+