diff --git a/docs/apikeys.mdx b/docs/apikeys.mdx
index 38626760eb..7afe96a8bf 100644
--- a/docs/apikeys.mdx
+++ b/docs/apikeys.mdx
@@ -1,54 +1,188 @@
---
title: "API keys"
-description: "How to authenticate with Trigger.dev so you can trigger tasks."
+description: "Authenticate backend requests with environment-specific API keys and restrict additional keys to the access each integration needs."
---
-### Authentication and your secret keys
+**API keys authenticate backend requests to a specific Trigger.dev project and environment.** Each environment has a root key, and you can create additional named keys for separate services and integrations.
-When you [trigger a task](/triggering) from your backend code, you need to set the `TRIGGER_SECRET_KEY` environment variable.
+
+ API keys grant access to your Trigger.dev environment. Store them in a secret manager or backend
+ environment variable, never commit them to source control, and never expose them in frontend code.
+
-Each environment has its own secret key. You can find the value on the API keys page in the Trigger.dev dashboard:
+## Root and additional keys
-
+Each environment has two types of API key:
+
+| Key type | Access | Secret visibility | Lifecycle |
+| --- | --- | --- | --- |
+| **Root key** | Full access to the environment | Available to copy from the dashboard | Regenerate it with a 24-hour grace period for the previous value |
+| **Additional key** | Full or restricted access | Shown once when created, then stored as a hash and displayed only by its suffix | Set an optional expiration date or revoke it immediately |
+
+Root keys use prefixes such as `tr_dev_`, `tr_stg_`, `tr_prod_`, and `tr_preview_`. Additional keys include `_sk_` after the environment, for example `tr_prod_sk_…`.
+
+Use additional keys to give each backend service, deployment pipeline, or external integration its own credential. This lets you revoke one integration without rotating the environment's root key.
+
+## Find your API keys
+
+Open your project in the dashboard, select an environment, and open the **API keys** page.
+
+API keys belong to one environment. A Development key cannot access Production, and a Production key cannot access Staging.
- For preview branches, you need to also set the `TRIGGER_PREVIEW_BRANCH` environment variable as
- well. You can find the value on the API keys page when you're on the preview branch.
+ Every team member has their own Development environment and keys. Copy the Development key from
+ your own API keys page so local requests run against your machine.
-### Automatically Configuring the SDK
+## Configure the SDK
-To automatically configure the SDK with your secret key, you can set the `TRIGGER_SECRET_KEY` environment variable. The SDK will automatically use this value when calling API methods (like `trigger`).
+Set `TRIGGER_SECRET_KEY` in your backend environment. The SDK reads it automatically for operations such as triggering tasks and retrieving runs.
```bash .env
-TRIGGER_SECRET_KEY="tr_dev_…"
-TRIGGER_PREVIEW_BRANCH="my-branch" # Only needed for preview branches
+TRIGGER_SECRET_KEY="tr_prod_…"
+```
+
+Additional keys use the same environment variable:
+
+```bash .env
+TRIGGER_SECRET_KEY="tr_prod_sk_…"
+```
+
+To configure the SDK in code, pass the key to `configure`:
+
+```ts Your backend code
+import { configure, tasks } from "@trigger.dev/sdk";
+
+configure({
+ secretKey: process.env.TRIGGER_SECRET_KEY,
+});
+
+await tasks.trigger("send-email", {
+ to: "user@example.com",
+});
```
-You can do the same if you are self-hosting and need to change the default URL by using `TRIGGER_API_URL`.
+If you self-host Trigger.dev, set `TRIGGER_API_URL` or pass `baseURL` to `configure`:
```bash .env
+TRIGGER_SECRET_KEY="tr_prod_…"
TRIGGER_API_URL="https://trigger.example.com"
-TRIGGER_PREVIEW_BRANCH="my-branch" # Only needed for preview branches
```
-The default URL is `https://api.trigger.dev`.
+The default API URL is `https://api.trigger.dev`.
-### Manually Configuring the SDK
+## Create an additional key
-If you prefer to manually configure the SDK, you can call the `configure` method:
+Create a separate key for each service or integration that accesses Trigger.dev.
-```ts
-import { configure } from "@trigger.dev/sdk";
-import { myTask } from "./trigger/myTasks";
+
+ Creating and revoking keys requires permission to manage API keys for the selected environment.
+ The dashboard disables these actions when your role does not have permission.
+
-configure({
- secretKey: "tr_dev_1234", // WARNING: Never actually hardcode your secret key like this
- previewBranch: "my-branch", // Only needed for preview branches
- baseURL: "https://mytrigger.example.com", // Optional
-});
+
+
+ Select the project and environment the integration needs to access, then open **API keys**.
+
+
+ Click **New API key**, enter a descriptive name, and optionally set an expiration date. Names can
+ contain up to 64 characters.
+
+
+ Select an access preset. For task-aware presets, choose all tasks or up to 10 task identifiers.
+
+
+ Copy the key into your secret manager or backend environment. Trigger.dev shows the complete
+ value only once.
+
+
+
+The dashboard displays the key's status, access, creator, creation time, and last-used time without storing or revealing its plaintext value.
+
+## Access presets
+
+Access presets define what an additional key can do. The dashboard shows which presets are available for your plan.
+
+| Preset | Access |
+| --- | --- |
+| **Trigger only** | Trigger runs and batches for all or selected tasks. Trigger responses include scoped public access tokens for the runs and batches they create |
+| **Task operator** | Trigger all or selected tasks and inspect or operate on their runs |
+| **Environment observer** | Read runs, tasks, batches, logs, traces, and queues |
+| **Environment operator** | Observe and operate on runs and queues, and trigger tasks |
+| **Deploy only** | Deploy new versions, read deployment status, and read environment variables |
+| **Environment variables only** | Read and write environment variables in this environment |
+| **No restrictions** | Full access to the environment, matching the root key |
-async function triggerTask() {
- await myTask.trigger({ userId: "1234" }); // This will use the secret key and base URL you configured
-}
+**Trigger only** and **Task operator** can be restricted to selected tasks. Task restrictions use task identifiers, such as `send-email`, and continue to apply across deployments. A request involving multiple tasks succeeds only when the key can access every task in the request.
+
+Access is fixed when you create a key. Changes to a preset do not alter existing keys. To change a key's access, revoke it and create a replacement.
+
+
+ Streamed batch ingestion is not atomic. When using a task-restricted key, earlier items may be
+ accepted before a later item fails validation or authorization.
+
+
+## Expire and revoke additional keys
+
+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.
+
+Removing a team member does not revoke additional keys they created. Review and revoke their keys separately when their access changes.
+
+## Regenerate the root key
+
+Regenerating the root key creates a new value immediately. The previous root key remains valid for 24 hours so you can update services without downtime, then stops authenticating.
+
+Public access tokens signed with the previous root key remain valid until the earlier of their own expiration and the end of the 24-hour grace period. Additional-key revocation does not have a grace period.
+
+## Create public access tokens
+
+Additional keys can create scoped [Public Access Tokens](/realtime/auth) without receiving the environment's root key. Use `@trigger.dev/sdk` version 4.5.8 or later with additional keys.
+
+When an additional key calls `auth.createPublicToken()`:
+
+- The token must request at least one scope.
+- Its scopes cannot exceed the additional key's access.
+- It expires after 15 minutes by default.
+- Its expiration cannot exceed 30 days.
+- A numeric `expirationTime` is a Unix timestamp in seconds.
+
+Revoking or expiring an additional key does not revoke tokens it already created. Those tokens remain valid until their own expiration, unless the environment's root key is regenerated first.
+
+## Target Preview and Development branches
+
+Preview and named Development branches use their parent environment's key set. Select the branch by setting `TRIGGER_PREVIEW_BRANCH` alongside the environment key:
+
+```bash .env
+TRIGGER_SECRET_KEY="tr_preview_…"
+TRIGGER_PREVIEW_BRANCH="feature/new-checkout"
```
+
+The SDK sends the branch automatically. When calling the API directly, send the same value in the `x-trigger-branch` header.
+
+## Self-hosting
+
+Self-hosted installations support multiple additional keys with **No restrictions**. The restricted access presets are available in Trigger.dev Cloud.
+
+Keep your instance and SDK current before creating additional keys. Calling a public-token API with an additional key on a server that does not support server-minted tokens returns an upgrade error; use the root key until the server is upgraded.
+
+## Security recommendations
+
+- Create one additional key per service or integration instead of sharing the root key.
+- Choose the narrowest access preset and task selection that supports the integration.
+- Store keys in a secret manager and inject them as backend environment variables.
+- Set expiration dates for temporary integrations and deployment credentials.
+- Revoke keys when an integration or team member no longer needs access.
+- Never put an API key in frontend code. Use scoped [Public Access Tokens](/realtime/auth) for client-side access.
+
+## Next steps
+
+
+
+ Trigger tasks from your backend with an environment API key.
+
+
+ Create scoped public tokens for frontend and realtime access.
+
+