From b189395334407f4383a45d5d20671027ba51ba24 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 20:37:35 +0000 Subject: [PATCH 1/4] [docs] Document authenticating from outside a Vercel deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inside a deployment the Vercel World authenticates itself with the per-request OIDC token the platform injects, so the docs only described the zero-configuration case. A local script or a CI job has no such token, and the reference pages listed the WORKFLOW_VERCEL_* variables one by one without saying which ones go together or why. Adds a "Connecting from outside a deployment" section to the Vercel World page (v4 and v5) covering the token plus project ID, team ID, and environment; that projectId+teamId is what routes the client through the api-workflow proxy, which is the only path that accepts a Vercel auth token; that starting runs additionally needs VERCEL_DEPLOYMENT_ID; and a worked script and GitHub Actions example. Notes that a development-scoped VERCEL_OIDC_TOKEN from `vercel env pull` is not a substitute — the backend attributes requests to the environment in the token's claim, so it must not be used to reach production or preview data. Also drops a non-existent `baseUrl` option from the v4 programmatic configuration sample and uses ID-shaped placeholders there, since projectId/teamId are IDs rather than slugs. Signed-off-by: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> Co-Authored-By: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> --- .changeset/docs-external-vercel-auth.md | 4 + .../docs/v5/configuration/cli-and-web-ui.mdx | 2 + docs/content/docs/v5/configuration/worlds.mdx | 3 + docs/content/worlds/v4/vercel.mdx | 144 +++++++++++++++++- docs/content/worlds/v5/vercel.mdx | 144 +++++++++++++++++- 5 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 .changeset/docs-external-vercel-auth.md diff --git a/.changeset/docs-external-vercel-auth.md b/.changeset/docs-external-vercel-auth.md new file mode 100644 index 0000000000..144996bf03 --- /dev/null +++ b/.changeset/docs-external-vercel-auth.md @@ -0,0 +1,4 @@ +--- +--- + +Document authenticating the Vercel World from outside a deployment (local scripts and CI) with a Vercel auth token. diff --git a/docs/content/docs/v5/configuration/cli-and-web-ui.mdx b/docs/content/docs/v5/configuration/cli-and-web-ui.mdx index 6694e487ca..acb5c577ea 100644 --- a/docs/content/docs/v5/configuration/cli-and-web-ui.mdx +++ b/docs/content/docs/v5/configuration/cli-and-web-ui.mdx @@ -12,6 +12,8 @@ The `workflow` CLI uses flags first, then environment variables, then defaults o Vercel project and auth settings can often be inferred from `.vercel/project.json` and your Vercel CLI login. +Whichever way they are resolved, the CLI ends up building a Vercel World from an auth token plus a project ID, team ID, and environment — the same four values any external client needs. Set them explicitly when there is no interactive `vercel login` to infer from, or when writing your own script instead of using the CLI; see [Connecting from outside a deployment](/worlds/vercel#connecting-from-outside-a-deployment). + ## Target backend ### `--backend` / `-b` diff --git a/docs/content/docs/v5/configuration/worlds.mdx b/docs/content/docs/v5/configuration/worlds.mdx index 72487e4a1f..f3f93b1401 100644 --- a/docs/content/docs/v5/configuration/worlds.mdx +++ b/docs/content/docs/v5/configuration/worlds.mdx @@ -192,6 +192,8 @@ The Vercel World is configured automatically inside Vercel deployments. The plat Most applications should not set `WORKFLOW_VERCEL_*` variables on Vercel. They configure tooling that talks to a Vercel Workflow project from outside a deployment, such as the `workflow` CLI, the web UI, CI, or tests. The runtime warns if these variables are set in a deployed Vercel function because they do not control runtime configuration there. +The entries below are the individual knobs. For a walkthrough of authenticating a local script or a CI job with a Vercel auth token — which variables are needed together, and how to pass them to the World factory — see [Connecting from outside a deployment](/worlds/vercel#connecting-from-outside-a-deployment). + Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, and `VERCEL_DEPLOYMENT_KEY` are read by the runtime inside Vercel deployments. Do not set them yourself. ### `token` @@ -200,6 +202,7 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an - CLI flag: `--authToken` - Default: inferred when possible - Vercel API token for external tooling. Keep it secret. +- Only meaningful alongside `projectConfig.projectId` and `projectConfig.teamId`: those three together route the client through the `api-workflow` proxy, which is the only path that accepts a Vercel auth token. Supplying both IDs but no token throws rather than failing later with a `401`. ### `projectConfig.environment` diff --git a/docs/content/worlds/v4/vercel.mdx b/docs/content/worlds/v4/vercel.mdx index f2352126d9..347fe540fb 100644 --- a/docs/content/worlds/v4/vercel.mdx +++ b/docs/content/worlds/v4/vercel.mdx @@ -92,6 +92,139 @@ npx workflow inspect runs \ Learn more in the [Observability](/docs/observability) documentation. +## Connecting from outside a deployment + +Inside a Vercel deployment there is nothing to authenticate: the platform injects a per-request OIDC token and the Vercel World uses it automatically. A process that is *not* a deployment has no such token — a script on your laptop, a CI job driving end-to-end tests against a preview, a one-off backfill. Those clients authenticate with a **Vercel auth token** and route their requests through the `api-workflow` proxy on `api.vercel.com`, which checks the token against your team and project permissions and attributes the request to the environment you name. + + + Do not reuse the `VERCEL_OIDC_TOKEN` that `vercel env pull` writes into + `.env.local` as your Workflow credential. That token is scoped to your + project's **development** environment, and the backend attributes every + request to the environment in the token's claim — so it addresses + development data, not your `production` or `preview` runs, and it should not + be used to reach across that boundary. A pulled OIDC token is still the right + thing for [bypassing deployment + protection](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources) + when your script has to fetch a protected preview URL; that is a separate + credential from the one that talks to the Workflow API. + + +### Environment variables + +These four values are what an external client needs. The names are the convention shared by the `workflow` CLI, the web UI, and Workflow's own CI: + +| Variable | Value | Notes | +| --- | --- | --- | +| `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | +| `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required — see [below](#why-both-ids-are-required). | +| `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required — see [below](#why-both-ids-are-required). | +| `WORKFLOW_VERCEL_ENV` | `production` or `preview` | Defaults to `production`. | + +Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page. + +Three more are situational: + +- `VERCEL_DEPLOYMENT_ID` — required to **start** runs, not to read them. See [Starting runs](#starting-runs-from-outside-a-deployment). +- `WORKFLOW_VERCEL_PROJECT_NAME` — the project slug, used only to build dashboard links. +- `VERCEL_TOKEN` — the one token variable the World itself reads, and only as a fallback for the direct Vercel API calls it makes alongside the proxy (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it. + + + The Workflow SDK does not read `WORKFLOW_VERCEL_*` from the environment on + your behalf — the runtime's own `createWorld()` deliberately ignores them, so + that a stray variable on a deployed project cannot redirect a live + application. Read them in your own code and pass them to the World factory, + as below. Setting them on a deployed Vercel project has no effect on runtime + configuration, and the runtime warns when it sees them there. + + +### Wiring it up in a script + +Build the Vercel World with an explicit config and install it with [`setWorld()`](/docs/api-reference/workflow-runtime/set-world) before the first `workflow/api` call: + +{/*@skip-typecheck: incomplete code sample*/} + +```typescript title="scripts/start-run.ts" lineNumbers +import { start } from "workflow/api"; +import { setWorld } from "workflow/runtime"; +import { createVercelWorld } from "@workflow/world-vercel"; +import { myWorkflow } from "../workflows/my-workflow"; + +setWorld( + createVercelWorld({ + token: process.env.WORKFLOW_VERCEL_AUTH_TOKEN, // [!code highlight] + projectConfig: { + projectId: process.env.WORKFLOW_VERCEL_PROJECT, // [!code highlight] + teamId: process.env.WORKFLOW_VERCEL_TEAM, // [!code highlight] + environment: process.env.WORKFLOW_VERCEL_ENV, // [!code highlight] + }, + }) +); + +const run = await start(myWorkflow, ["input"]); +console.log(run.runId); +``` + +Behind the scenes the client now sends every Workflow request to `https://api.vercel.com/v1/workflow` with `Authorization: Bearer `, plus `x-vercel-team-id`, `x-vercel-project-id`, and `x-vercel-environment` headers derived from the config. The same token also authorizes the per-run key fetch that decrypts run and step data, so reads come back as values rather than opaque bytes — see [Encryption](/docs/how-it-works/encryption). + +This is exactly what the `workflow` CLI does under the hood. It resolves the same four values — from `--authToken` / `--project` / `--team` / `--env`, then the `WORKFLOW_VERCEL_*` variables, then `.vercel/project.json` and your `vercel login` credentials — and constructs the World with them. Passing them explicitly is the non-interactive equivalent of being logged in and linked. + +#### Why both IDs are required + +The project ID and team ID together are what put the client on the proxy. With both present it uses the proxy and a Vercel auth token; with either missing it talks to workflow-server directly, and that path authenticates with a deployment's per-request OIDC token — a credential an external process does not have. Supply all of `token`, `projectId`, and `teamId`, or none of them. + +Supplying both IDs but no token fails immediately, with a message naming the missing token, rather than surfacing later as an opaque `401`. + +### Starting runs from outside a deployment + +[`start()`](/docs/api-reference/workflow-api/start) addresses its queue message to one deployment, because a run executes the code of the deployment that created it. Inside a deployment that is the current one; outside, there is nothing to infer, so set `VERCEL_DEPLOYMENT_ID` to the deployment whose code should run the workflow: + +```bash +# `vercel inspect` prints the dpl_... ID for a deployment URL +vercel inspect https://my-app-abc123.vercel.app +``` + +Reads such as [`getRun()`](/docs/api-reference/workflow-api/get-run) do not need it. `start(..., { deploymentId: "latest" })` does: it resolves the newest deployment sharing that deployment's environment, so it needs one to start from. + + + Keep `WORKFLOW_VERCEL_ENV` and the environment of the deployment named by + `VERCEL_DEPLOYMENT_ID` aligned. The run is created in the environment you + authenticate as, while its queue message is addressed to the deployment you + name — a run whose two halves land in different environments will not + execute as expected. + + +### In CI + +Wait for the deployment under test, then hand its ID to the test step alongside the four auth variables. This is how Workflow's own end-to-end suite authenticates against its preview and production deployments: + +```yaml title=".github/workflows/e2e.yml" lineNumbers +jobs: + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Wait for the Vercel deployment + id: deployment + uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037 + with: + project-slug: my-app + environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + + - name: Run end-to-end tests + run: pnpm test:e2e + env: + WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_TOKEN }} + WORKFLOW_VERCEL_TEAM: team_xxxxxxxxxxxxxxxx + WORKFLOW_VERCEL_PROJECT: prj_xxxxxxxxxxxxxxxx + WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + VERCEL_DEPLOYMENT_ID: ${{ steps.deployment.outputs.deployment-id }} +``` + +Store the token as an encrypted secret and derive `WORKFLOW_VERCEL_ENV` from the branch, so a pull request run never authenticates as `production`. + +If the deployment is behind deployment protection, the job also needs a way to reach its URL. Grant the workflow `id-token: write` and mint a short-lived OIDC token from the runner for the `x-vercel-trusted-oidc-idp-token` header; that credential is for [Trusted Sources](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources) only and is separate from the Vercel auth token above. + ## Testing & Compatibility @@ -100,6 +233,8 @@ Learn more in the [Observability](/docs/observability) documentation. The Vercel World requires no configuration when deployed to Vercel. For advanced use cases, you can override settings programmatically via `createVercelWorld()`. +The `WORKFLOW_VERCEL_*` variables below configure tooling that talks to a Vercel Workflow project from outside a deployment. For the task-oriented version — which ones a local script or CI job needs, and how to hand them to the SDK — see [Connecting from outside a deployment](#connecting-from-outside-a-deployment). + ### `WORKFLOW_VERCEL_ENV` The Vercel environment to use. Options: `production`, `preview`, `development`. Automatically detected. @@ -134,6 +269,8 @@ Because it routes each run's flow invocations through a dedicated `maxConcurrenc ### Programmatic configuration +`createVercelWorld()` accepts explicit API configuration. It does not read `WORKFLOW_VERCEL_*` automatically, so pass the environment values yourself. `projectId` and `teamId` are IDs, not slugs. To install the result as the World your app uses, see [Wiring it up in a script](#wiring-it-up-in-a-script); to point the client at a different proxy, use `WORKFLOW_VERCEL_BACKEND_URL`. + {/*@skip-typecheck: incomplete code sample*/} ```typescript title="workflow.config.ts" lineNumbers @@ -141,10 +278,9 @@ import { createVercelWorld } from "@workflow/world-vercel"; const world = createVercelWorld({ token: process.env.WORKFLOW_VERCEL_AUTH_TOKEN, - baseUrl: "https://api.vercel.com/v1/workflow", projectConfig: { - projectId: "my-project", - teamId: "my-team", + projectId: "prj_...", + teamId: "team_...", environment: "production", }, }); @@ -197,6 +333,6 @@ The Vercel World uses Vercel's infrastructure for workflow execution: - **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and [end-to-end encryption](/docs/how-it-works/encryption) - **Queuing** - Steps are distributed across serverless functions via [Vercel Queues](https://vercel.com/docs/queues) with automatic retries and [consumer function security](#consumer-function-security) -- **Authentication** - OIDC tokens provide secure, automatic authentication +- **Authentication** - OIDC tokens provide secure, automatic authentication inside deployments; clients outside one authenticate with a [Vercel auth token](#connecting-from-outside-a-deployment) For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/workflows). diff --git a/docs/content/worlds/v5/vercel.mdx b/docs/content/worlds/v5/vercel.mdx index ac96611222..8310f80fb5 100644 --- a/docs/content/worlds/v5/vercel.mdx +++ b/docs/content/worlds/v5/vercel.mdx @@ -128,6 +128,144 @@ npx workflow inspect runs \ Learn more in the [Observability](/docs/observability) documentation. +## Connecting from outside a deployment + +Inside a Vercel deployment there is nothing to authenticate: the platform injects a per-request OIDC token and the Vercel World uses it automatically. A process that is *not* a deployment has no such token — a script on your laptop, a CI job driving end-to-end tests against a preview, a one-off backfill. Those clients authenticate with a **Vercel auth token** and route their requests through the `api-workflow` proxy on `api.vercel.com`, which checks the token against your team and project permissions and attributes the request to the environment you name. + + + Do not reuse the `VERCEL_OIDC_TOKEN` that `vercel env pull` writes into + `.env.local` as your Workflow credential. That token is scoped to your + project's **development** environment, and the backend attributes every + request to the environment in the token's claim — so it addresses + development data, not your `production` or `preview` runs, and it should not + be used to reach across that boundary. A pulled OIDC token is still the right + thing for [bypassing deployment + protection](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources) + when your script has to fetch a protected preview URL; that is a separate + credential from the one that talks to the Workflow API. + + +### Environment variables + +These four values are what an external client needs. The names are the convention shared by the `workflow` CLI, the web UI, and Workflow's own CI: + +| Variable | Value | Notes | +| --- | --- | --- | +| `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | +| `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required — see [below](#why-both-ids-are-required). | +| `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required — see [below](#why-both-ids-are-required). | +| `WORKFLOW_VERCEL_ENV` | `production`, `preview`, or a custom environment slug | Defaults to `production`. | + +Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page. + +Three more are situational: + +- `VERCEL_DEPLOYMENT_ID` — required to **start** runs, not to read them. See [Starting runs](#starting-runs-from-outside-a-deployment). +- `WORKFLOW_VERCEL_PROJECT_NAME` — the project slug, used only to build dashboard links. +- `VERCEL_TOKEN` — the one token variable the World itself reads, and only as a fallback for the direct Vercel API calls it makes alongside the proxy (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it. + +For a custom environment, set `WORKFLOW_VERCEL_ENV` to the environment's **slug**, not its ID. The proxy accepts either but always attributes the write to the slug, and it is the slug that the consuming deployment compares against. + + + The Workflow SDK does not read `WORKFLOW_VERCEL_*` from the environment on + your behalf — the runtime's own `createWorld()` deliberately ignores them, so + that a stray variable on a deployed project cannot redirect a live + application. Read them in your own code and pass them to the World factory, + as below. Setting them on a deployed Vercel project has no effect on runtime + configuration, and the runtime warns when it sees them there. + + +### Wiring it up in a script + +Build the Vercel World with an explicit config and install it with [`setWorld()`](/docs/api-reference/workflow-runtime/set-world) before the first `workflow/api` call: + +{/*@skip-typecheck: incomplete code sample*/} + +```typescript title="scripts/start-run.ts" lineNumbers +import { start } from "workflow/api"; +import { setWorld } from "workflow/runtime"; +import { createWorld } from "@workflow/world-vercel"; +import { myWorkflow } from "../workflows/my-workflow"; + +setWorld( + createWorld({ + token: process.env.WORKFLOW_VERCEL_AUTH_TOKEN, // [!code highlight] + projectConfig: { + projectId: process.env.WORKFLOW_VERCEL_PROJECT, // [!code highlight] + teamId: process.env.WORKFLOW_VERCEL_TEAM, // [!code highlight] + environment: process.env.WORKFLOW_VERCEL_ENV, // [!code highlight] + }, + }) +); + +const run = await start(myWorkflow, ["input"]); +console.log(run.runId); +``` + +Behind the scenes the client now sends every Workflow request to `https://api.vercel.com/v1/workflow` with `Authorization: Bearer `, plus `x-vercel-team-id`, `x-vercel-project-id`, and `x-vercel-environment` headers derived from the config. The same token also authorizes the per-run key fetch that decrypts run and step data, so reads come back as values rather than opaque bytes — see [Encryption](/docs/how-it-works/encryption). + +This is exactly what the `workflow` CLI does under the hood. It resolves the same four values — from `--authToken` / `--project` / `--team` / `--env`, then the `WORKFLOW_VERCEL_*` variables, then `.vercel/project.json` and your `vercel login` credentials — and constructs the World with them. Passing them explicitly is the non-interactive equivalent of being logged in and linked. + +#### Why both IDs are required + +The project ID and team ID together are what put the client on the proxy. With both present it uses the proxy and a Vercel auth token; with either missing it talks to workflow-server directly, and that path authenticates with a deployment's per-request OIDC token — a credential an external process does not have. Supply all of `token`, `projectId`, and `teamId`, or none of them. + +Supplying both IDs but no token fails immediately, with a message naming the missing token, rather than surfacing later as an opaque `401`. + +### Starting runs from outside a deployment + +[`start()`](/docs/api-reference/workflow-api/start) addresses its queue message to one deployment, because a run executes the code of the deployment that created it. Inside a deployment that is the current one; outside, there is nothing to infer, so set `VERCEL_DEPLOYMENT_ID` to the deployment whose code should run the workflow: + +```bash +# `vercel inspect` prints the dpl_... ID for a deployment URL +vercel inspect https://my-app-abc123.vercel.app +``` + +Reads such as [`getRun()`](/docs/api-reference/workflow-api/get-run) do not need it. `start(..., { deploymentId: "latest" })` does: it resolves the newest deployment sharing that deployment's environment, so it needs one to start from. + + + The environment you authenticate as and the deployment you target must + agree. A client that writes a run to `preview` but pins the queue message to + a production deployment creates a run that never executes: the consuming + deployment compares the creator's environment against its own and discards + the delivery instead of forking the run into both tenants. If a run stays + `pending` and the deployment logs a refusal naming two environments, check + `WORKFLOW_VERCEL_ENV` against the environment of the deployment in + `VERCEL_DEPLOYMENT_ID`. + + +### In CI + +Wait for the deployment under test, then hand its ID to the test step alongside the four auth variables. This is how Workflow's own end-to-end suite authenticates against its preview and production deployments: + +```yaml title=".github/workflows/e2e.yml" lineNumbers +jobs: + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Wait for the Vercel deployment + id: deployment + uses: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037 + with: + project-slug: my-app + environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + + - name: Run end-to-end tests + run: pnpm test:e2e + env: + WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_TOKEN }} + WORKFLOW_VERCEL_TEAM: team_xxxxxxxxxxxxxxxx + WORKFLOW_VERCEL_PROJECT: prj_xxxxxxxxxxxxxxxx + WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }} + VERCEL_DEPLOYMENT_ID: ${{ steps.deployment.outputs.deployment-id }} +``` + +Store the token as an encrypted secret and derive `WORKFLOW_VERCEL_ENV` from the branch, so a pull request run never authenticates as `production`. + +If the deployment is behind deployment protection, the job also needs a way to reach its URL. Grant the workflow `id-token: write` and mint a short-lived OIDC token from the runner for the `x-vercel-trusted-oidc-idp-token` header; that credential is for [Trusted Sources](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources) only and is separate from the Vercel auth token above. + ## Testing & Compatibility @@ -142,6 +280,8 @@ Most users never need to set the `WORKFLOW_VERCEL_*` variables below. They are o For example, you might set them when running `workflow inspect runs --backend vercel` from CI without an interactive `vercel login`, or when running tests against a specific preview deployment. In normal local development, the CLI infers these values from your `.vercel` directory and Vercel CLI login. In a deployed Vercel function, these variables have no effect on runtime configuration, and the runtime warns if they are set there. +For the task-oriented version of this — which variables a local script or CI job needs, and how to hand them to the SDK — see [Connecting from outside a deployment](#connecting-from-outside-a-deployment). + ### `WORKFLOW_VERCEL_ENV` The Vercel environment to target. Options: `production`, `preview`. Default: `production`. @@ -216,7 +356,7 @@ The WebSocket handshake is itself a span, `workflow.events.ws.connect`, so the c ### Programmatic configuration -`createWorld()` accepts explicit API configuration. It does not read `WORKFLOW_VERCEL_*` automatically, so pass the environment values yourself when you want a configured World module: +`createWorld()` accepts explicit API configuration. It does not read `WORKFLOW_VERCEL_*` automatically, so pass the environment values yourself when you want a configured World module. To configure a client at the point of use instead of through `WORKFLOW_TARGET_WORLD`, see [Wiring it up in a script](#wiring-it-up-in-a-script). {/*@skip-typecheck: incomplete code sample*/} @@ -284,6 +424,6 @@ The Vercel World uses Vercel's infrastructure for workflow execution: - **Storage** - Workflow data is stored in Vercel's cloud with automatic replication and [end-to-end encryption](/docs/how-it-works/encryption) - **Queuing** - Steps are distributed across serverless functions via [Vercel Queues](https://vercel.com/docs/queues) with automatic retries and [consumer function security](#consumer-function-security) -- **Authentication** - OIDC tokens provide secure, automatic authentication +- **Authentication** - OIDC tokens provide secure, automatic authentication inside deployments; clients outside one authenticate with a [Vercel auth token](#connecting-from-outside-a-deployment) For more details, see the [Vercel Workflow documentation](https://vercel.com/docs/workflows). From 3e5e54630ab466f017f676226515aacc394a0a6e Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 20:46:39 +0000 Subject: [PATCH 2/4] [docs] Reconcile WORKFLOW_VERCEL_ENV values across the pages The new section documented custom environment slugs while the reference entries on the same pages still listed only production/preview (v5) or production/preview/development (v4), so the two disagreed. The env-var table now names the variable and defers to the reference entry for the accepted values, keeping one canonical list per page. The v5 reference entry gains custom environment slugs, matching what the proxy accepts on the projectConfig path, and the v5 configuration reference records that the proxy takes a custom environment's slug or ID but attributes the write to the slug. Signed-off-by: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> Co-Authored-By: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> --- docs/content/docs/v5/configuration/worlds.mdx | 2 +- docs/content/worlds/v4/vercel.mdx | 2 +- docs/content/worlds/v5/vercel.mdx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/v5/configuration/worlds.mdx b/docs/content/docs/v5/configuration/worlds.mdx index f3f93b1401..18e0ccf4c8 100644 --- a/docs/content/docs/v5/configuration/worlds.mdx +++ b/docs/content/docs/v5/configuration/worlds.mdx @@ -209,7 +209,7 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an - Environment variable: `WORKFLOW_VERCEL_ENV` - CLI flag: `--env` or `-e` - Default: `production` -- Vercel environment targeted by tooling. Accepts `production` or `preview`. +- Vercel environment targeted by tooling. Accepts `production`, `preview`, or a custom environment's slug — the proxy also accepts a custom environment's ID but attributes the write to its slug, so configure the slug. ### `projectConfig.projectId` diff --git a/docs/content/worlds/v4/vercel.mdx b/docs/content/worlds/v4/vercel.mdx index 347fe540fb..443d394b26 100644 --- a/docs/content/worlds/v4/vercel.mdx +++ b/docs/content/worlds/v4/vercel.mdx @@ -118,7 +118,7 @@ These four values are what an external client needs. The names are the conventio | `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | | `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required — see [below](#why-both-ids-are-required). | | `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required — see [below](#why-both-ids-are-required). | -| `WORKFLOW_VERCEL_ENV` | `production` or `preview` | Defaults to `production`. | +| `WORKFLOW_VERCEL_ENV` | The environment to target | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env) for the accepted values. | Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page. diff --git a/docs/content/worlds/v5/vercel.mdx b/docs/content/worlds/v5/vercel.mdx index 8310f80fb5..2605f7644f 100644 --- a/docs/content/worlds/v5/vercel.mdx +++ b/docs/content/worlds/v5/vercel.mdx @@ -154,7 +154,7 @@ These four values are what an external client needs. The names are the conventio | `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | | `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required — see [below](#why-both-ids-are-required). | | `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required — see [below](#why-both-ids-are-required). | -| `WORKFLOW_VERCEL_ENV` | `production`, `preview`, or a custom environment slug | Defaults to `production`. | +| `WORKFLOW_VERCEL_ENV` | The environment to target | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env) for the accepted values. | Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page. @@ -284,7 +284,7 @@ For the task-oriented version of this — which variables a local script or CI j ### `WORKFLOW_VERCEL_ENV` -The Vercel environment to target. Options: `production`, `preview`. Default: `production`. +The Vercel environment to target. Options: `production`, `preview`, or a custom environment's slug. Default: `production`. See [Environment variables](#environment-variables) for how a custom environment is resolved. ### `WORKFLOW_VERCEL_AUTH_TOKEN` From 081e7edbb9f42f79caa81f0969e79956d7677490 Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 21:15:10 +0000 Subject: [PATCH 3/4] [docs] Describe the external client, not the backend that serves it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The external-auth section explained how the request is carried as well as what the caller has to set: the api-workflow proxy on api.vercel.com, the x-vercel-* headers derived from the config, and the direct workflow-server path taken when the two IDs are absent. None of that is something a reader can act on, and all of it is free to change without notice. The guidance itself is unchanged — same four values, same script, same CI snippet, same warnings about the pulled OIDC token and about environment alignment. What the reader now gets is the contract: authenticate with a Vercel auth token, set the token and both IDs together, name the environment you mean. "Why both IDs are required" was that reasoning in section form, so it is gone; the one actionable sentence it carried — set all three or none, and setting the IDs alone fails up front rather than as a 401 — moves inline under the sample, and the env-var tables say "Required" without pointing at a section that explains the backend's routing. Also finishes Copilot's remaining review thread on the v5 configuration reference: projectConfig.environment now lists custom environment slugs alongside production and preview, with the slug-not-ID caveat stated as a rule for the reader rather than as a description of what the proxy accepts. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> Co-Authored-By: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> --- docs/content/docs/v5/configuration/worlds.mdx | 4 +-- docs/content/worlds/v4/vercel.mdx | 28 +++++++-------- docs/content/worlds/v5/vercel.mdx | 35 ++++++++----------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/docs/content/docs/v5/configuration/worlds.mdx b/docs/content/docs/v5/configuration/worlds.mdx index 18e0ccf4c8..8a43d1d8f5 100644 --- a/docs/content/docs/v5/configuration/worlds.mdx +++ b/docs/content/docs/v5/configuration/worlds.mdx @@ -202,14 +202,14 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an - CLI flag: `--authToken` - Default: inferred when possible - Vercel API token for external tooling. Keep it secret. -- Only meaningful alongside `projectConfig.projectId` and `projectConfig.teamId`: those three together route the client through the `api-workflow` proxy, which is the only path that accepts a Vercel auth token. Supplying both IDs but no token throws rather than failing later with a `401`. +- Only meaningful alongside `projectConfig.projectId` and `projectConfig.teamId` — set all three together. Supplying both IDs but no token throws rather than failing later with a `401`. ### `projectConfig.environment` - Environment variable: `WORKFLOW_VERCEL_ENV` - CLI flag: `--env` or `-e` - Default: `production` -- Vercel environment targeted by tooling. Accepts `production`, `preview`, or a custom environment's slug — the proxy also accepts a custom environment's ID but attributes the write to its slug, so configure the slug. +- Vercel environment targeted by tooling. Accepts `production`, `preview`, or a custom environment's **slug** — configure the slug, not the environment's ID. ### `projectConfig.projectId` diff --git a/docs/content/worlds/v4/vercel.mdx b/docs/content/worlds/v4/vercel.mdx index 443d394b26..74c0f06649 100644 --- a/docs/content/worlds/v4/vercel.mdx +++ b/docs/content/worlds/v4/vercel.mdx @@ -94,7 +94,7 @@ Learn more in the [Observability](/docs/observability) documentation. ## Connecting from outside a deployment -Inside a Vercel deployment there is nothing to authenticate: the platform injects a per-request OIDC token and the Vercel World uses it automatically. A process that is *not* a deployment has no such token — a script on your laptop, a CI job driving end-to-end tests against a preview, a one-off backfill. Those clients authenticate with a **Vercel auth token** and route their requests through the `api-workflow` proxy on `api.vercel.com`, which checks the token against your team and project permissions and attributes the request to the environment you name. +Inside a Vercel deployment there is nothing to authenticate: the platform injects a per-request OIDC token and the Vercel World uses it automatically. A process that is *not* a deployment has no such token — a script on your laptop, a CI job driving end-to-end tests against a preview, a one-off backfill. Those clients authenticate with a **Vercel auth token** instead. Vercel checks that token against your team and project permissions, and attributes the request to the environment you name. Do not reuse the `VERCEL_OIDC_TOKEN` that `vercel env pull` writes into @@ -116,8 +116,8 @@ These four values are what an external client needs. The names are the conventio | Variable | Value | Notes | | --- | --- | --- | | `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | -| `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required — see [below](#why-both-ids-are-required). | -| `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required — see [below](#why-both-ids-are-required). | +| `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required. Set it together with the token and the project ID. | +| `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required. Set it together with the token and the team ID. | | `WORKFLOW_VERCEL_ENV` | The environment to target | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env) for the accepted values. | Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page. @@ -126,7 +126,7 @@ Three more are situational: - `VERCEL_DEPLOYMENT_ID` — required to **start** runs, not to read them. See [Starting runs](#starting-runs-from-outside-a-deployment). - `WORKFLOW_VERCEL_PROJECT_NAME` — the project slug, used only to build dashboard links. -- `VERCEL_TOKEN` — the one token variable the World itself reads, and only as a fallback for the direct Vercel API calls it makes alongside the proxy (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it. +- `VERCEL_TOKEN` — a fallback the World reads for the Vercel API calls it makes alongside your Workflow requests (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it. The Workflow SDK does not read `WORKFLOW_VERCEL_*` from the environment on @@ -164,19 +164,15 @@ const run = await start(myWorkflow, ["input"]); console.log(run.runId); ``` -Behind the scenes the client now sends every Workflow request to `https://api.vercel.com/v1/workflow` with `Authorization: Bearer `, plus `x-vercel-team-id`, `x-vercel-project-id`, and `x-vercel-environment` headers derived from the config. The same token also authorizes the per-run key fetch that decrypts run and step data, so reads come back as values rather than opaque bytes — see [Encryption](/docs/how-it-works/encryption). +Supply `token`, `projectId`, and `teamId` together. Setting the two IDs without a token fails immediately, with a message naming the missing token, rather than surfacing later as an opaque `401`. -This is exactly what the `workflow` CLI does under the hood. It resolves the same four values — from `--authToken` / `--project` / `--team` / `--env`, then the `WORKFLOW_VERCEL_*` variables, then `.vercel/project.json` and your `vercel login` credentials — and constructs the World with them. Passing them explicitly is the non-interactive equivalent of being logged in and linked. - -#### Why both IDs are required +The same token authorizes the per-run key fetch that decrypts run and step data, so reads come back as values rather than opaque bytes — see [Encryption](/docs/how-it-works/encryption). -The project ID and team ID together are what put the client on the proxy. With both present it uses the proxy and a Vercel auth token; with either missing it talks to workflow-server directly, and that path authenticates with a deployment's per-request OIDC token — a credential an external process does not have. Supply all of `token`, `projectId`, and `teamId`, or none of them. - -Supplying both IDs but no token fails immediately, with a message naming the missing token, rather than surfacing later as an opaque `401`. +This is exactly what the `workflow` CLI does under the hood. It resolves the same four values — from `--authToken` / `--project` / `--team` / `--env`, then the `WORKFLOW_VERCEL_*` variables, then `.vercel/project.json` and your `vercel login` credentials — and constructs the World with them. Passing them explicitly is the non-interactive equivalent of being logged in and linked. ### Starting runs from outside a deployment -[`start()`](/docs/api-reference/workflow-api/start) addresses its queue message to one deployment, because a run executes the code of the deployment that created it. Inside a deployment that is the current one; outside, there is nothing to infer, so set `VERCEL_DEPLOYMENT_ID` to the deployment whose code should run the workflow: +[`start()`](/docs/api-reference/workflow-api/start) has to name a deployment, because a run executes the code of the deployment that created it. Inside a deployment that is the current one; outside, there is nothing to infer, so set `VERCEL_DEPLOYMENT_ID` to the deployment whose code should run the workflow: ```bash # `vercel inspect` prints the dpl_... ID for a deployment URL @@ -188,9 +184,9 @@ Reads such as [`getRun()`](/docs/api-reference/workflow-api/get-run) do not need Keep `WORKFLOW_VERCEL_ENV` and the environment of the deployment named by `VERCEL_DEPLOYMENT_ID` aligned. The run is created in the environment you - authenticate as, while its queue message is addressed to the deployment you - name — a run whose two halves land in different environments will not - execute as expected. + authenticate as, but executes the code of the deployment you name — a run + whose two halves land in different environments will not execute as + expected. ### In CI @@ -269,7 +265,7 @@ Because it routes each run's flow invocations through a dedicated `maxConcurrenc ### Programmatic configuration -`createVercelWorld()` accepts explicit API configuration. It does not read `WORKFLOW_VERCEL_*` automatically, so pass the environment values yourself. `projectId` and `teamId` are IDs, not slugs. To install the result as the World your app uses, see [Wiring it up in a script](#wiring-it-up-in-a-script); to point the client at a different proxy, use `WORKFLOW_VERCEL_BACKEND_URL`. +`createVercelWorld()` accepts explicit API configuration. It does not read `WORKFLOW_VERCEL_*` automatically, so pass the environment values yourself. `projectId` and `teamId` are IDs, not slugs. To install the result as the World your app uses, see [Wiring it up in a script](#wiring-it-up-in-a-script). {/*@skip-typecheck: incomplete code sample*/} diff --git a/docs/content/worlds/v5/vercel.mdx b/docs/content/worlds/v5/vercel.mdx index 2605f7644f..620d245bb7 100644 --- a/docs/content/worlds/v5/vercel.mdx +++ b/docs/content/worlds/v5/vercel.mdx @@ -130,7 +130,7 @@ Learn more in the [Observability](/docs/observability) documentation. ## Connecting from outside a deployment -Inside a Vercel deployment there is nothing to authenticate: the platform injects a per-request OIDC token and the Vercel World uses it automatically. A process that is *not* a deployment has no such token — a script on your laptop, a CI job driving end-to-end tests against a preview, a one-off backfill. Those clients authenticate with a **Vercel auth token** and route their requests through the `api-workflow` proxy on `api.vercel.com`, which checks the token against your team and project permissions and attributes the request to the environment you name. +Inside a Vercel deployment there is nothing to authenticate: the platform injects a per-request OIDC token and the Vercel World uses it automatically. A process that is *not* a deployment has no such token — a script on your laptop, a CI job driving end-to-end tests against a preview, a one-off backfill. Those clients authenticate with a **Vercel auth token** instead. Vercel checks that token against your team and project permissions, and attributes the request to the environment you name. Do not reuse the `VERCEL_OIDC_TOKEN` that `vercel env pull` writes into @@ -152,8 +152,8 @@ These four values are what an external client needs. The names are the conventio | Variable | Value | Notes | | --- | --- | --- | | `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | -| `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required — see [below](#why-both-ids-are-required). | -| `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required — see [below](#why-both-ids-are-required). | +| `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required. Set it together with the token and the project ID. | +| `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required. Set it together with the token and the team ID. | | `WORKFLOW_VERCEL_ENV` | The environment to target | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env) for the accepted values. | Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page. @@ -162,9 +162,9 @@ Three more are situational: - `VERCEL_DEPLOYMENT_ID` — required to **start** runs, not to read them. See [Starting runs](#starting-runs-from-outside-a-deployment). - `WORKFLOW_VERCEL_PROJECT_NAME` — the project slug, used only to build dashboard links. -- `VERCEL_TOKEN` — the one token variable the World itself reads, and only as a fallback for the direct Vercel API calls it makes alongside the proxy (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it. +- `VERCEL_TOKEN` — a fallback the World reads for the Vercel API calls it makes alongside your Workflow requests (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it. -For a custom environment, set `WORKFLOW_VERCEL_ENV` to the environment's **slug**, not its ID. The proxy accepts either but always attributes the write to the slug, and it is the slug that the consuming deployment compares against. +For a custom environment, set `WORKFLOW_VERCEL_ENV` to the environment's **slug**, not its ID — the slug is what the consuming deployment compares against. The Workflow SDK does not read `WORKFLOW_VERCEL_*` from the environment on @@ -202,19 +202,15 @@ const run = await start(myWorkflow, ["input"]); console.log(run.runId); ``` -Behind the scenes the client now sends every Workflow request to `https://api.vercel.com/v1/workflow` with `Authorization: Bearer `, plus `x-vercel-team-id`, `x-vercel-project-id`, and `x-vercel-environment` headers derived from the config. The same token also authorizes the per-run key fetch that decrypts run and step data, so reads come back as values rather than opaque bytes — see [Encryption](/docs/how-it-works/encryption). +Supply `token`, `projectId`, and `teamId` together. Setting the two IDs without a token fails immediately, with a message naming the missing token, rather than surfacing later as an opaque `401`. -This is exactly what the `workflow` CLI does under the hood. It resolves the same four values — from `--authToken` / `--project` / `--team` / `--env`, then the `WORKFLOW_VERCEL_*` variables, then `.vercel/project.json` and your `vercel login` credentials — and constructs the World with them. Passing them explicitly is the non-interactive equivalent of being logged in and linked. - -#### Why both IDs are required +The same token authorizes the per-run key fetch that decrypts run and step data, so reads come back as values rather than opaque bytes — see [Encryption](/docs/how-it-works/encryption). -The project ID and team ID together are what put the client on the proxy. With both present it uses the proxy and a Vercel auth token; with either missing it talks to workflow-server directly, and that path authenticates with a deployment's per-request OIDC token — a credential an external process does not have. Supply all of `token`, `projectId`, and `teamId`, or none of them. - -Supplying both IDs but no token fails immediately, with a message naming the missing token, rather than surfacing later as an opaque `401`. +This is exactly what the `workflow` CLI does under the hood. It resolves the same four values — from `--authToken` / `--project` / `--team` / `--env`, then the `WORKFLOW_VERCEL_*` variables, then `.vercel/project.json` and your `vercel login` credentials — and constructs the World with them. Passing them explicitly is the non-interactive equivalent of being logged in and linked. ### Starting runs from outside a deployment -[`start()`](/docs/api-reference/workflow-api/start) addresses its queue message to one deployment, because a run executes the code of the deployment that created it. Inside a deployment that is the current one; outside, there is nothing to infer, so set `VERCEL_DEPLOYMENT_ID` to the deployment whose code should run the workflow: +[`start()`](/docs/api-reference/workflow-api/start) has to name a deployment, because a run executes the code of the deployment that created it. Inside a deployment that is the current one; outside, there is nothing to infer, so set `VERCEL_DEPLOYMENT_ID` to the deployment whose code should run the workflow: ```bash # `vercel inspect` prints the dpl_... ID for a deployment URL @@ -225,13 +221,12 @@ Reads such as [`getRun()`](/docs/api-reference/workflow-api/get-run) do not need The environment you authenticate as and the deployment you target must - agree. A client that writes a run to `preview` but pins the queue message to - a production deployment creates a run that never executes: the consuming - deployment compares the creator's environment against its own and discards - the delivery instead of forking the run into both tenants. If a run stays - `pending` and the deployment logs a refusal naming two environments, check - `WORKFLOW_VERCEL_ENV` against the environment of the deployment in - `VERCEL_DEPLOYMENT_ID`. + agree. A client that creates a run in `preview` but points it at a + production deployment creates a run that never executes: environments are + isolated from each other, so a deployment refuses work created in a + different one. If a run stays `pending` and the deployment logs a refusal + naming two environments, check `WORKFLOW_VERCEL_ENV` against the environment + of the deployment in `VERCEL_DEPLOYMENT_ID`. ### In CI From 82abefbcf9e7abf2a57d82e87cd6f1159028680b Mon Sep 17 00:00:00 2001 From: "vercel[bot]" <35613825+vercel[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 21:21:05 +0000 Subject: [PATCH 4/4] [docs] Stop presenting development as a supported environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WORKFLOW_VERCEL_ENV was documented three ways: the external-client tables deferred to the reference entry, the v5 reference entry listed production/preview plus custom environment slugs, and the v4 entry listed production/preview/development. Copilot flagged the mismatch on all three pages, suggesting `development` be added to the tables for consistency. `development` is not an environment an external client should target, so the inconsistency is resolved by dropping it rather than by spreading it to the pages that never mentioned it: - The env-var tables on both Vercel World pages name the accepted values inline (production, preview, or a custom environment's slug) and still link to the reference entry, so a reader does not have to follow a link to learn what is allowed. - The v4 reference entry drops `development` and gains custom environment slugs, matching v5. The v4 page also gains the slug-not-ID rule the v5 page already carried. - `--env` in the CLI and web UI reference accepts the same values, so it no longer says production/preview while the world reference and projectConfig.environment say otherwise. - The "isolates data per environment" bullet on both pages now reads production, preview, and any custom environments. - The `vercel env pull` warning no longer frames the pulled VERCEL_OIDC_TOKEN as addressing "development data". It states the consequence for the reader instead: requests made with that token are attributed to the environment carried in the token itself, so authenticate with a Vercel auth token. - The getHookByToken environment sample compared hook.environment against `VERCEL_ENV || "development"`, which could never match — the local world records hooks as `local`. It now prefers VERCEL_TARGET_ENV, which carries a custom environment's slug where VERCEL_ENV reports preview, and falls back to `local`. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> Co-Authored-By: Nathan Rajlich <71256+TooTallNate@users.noreply.github.com> --- .../workflow-api/get-hook-by-token.mdx | 6 +++++- .../workflow-api/get-hook-by-token.mdx | 6 +++++- .../docs/v5/configuration/cli-and-web-ui.mdx | 2 +- docs/content/worlds/v4/vercel.mdx | 20 ++++++++++--------- docs/content/worlds/v5/vercel.mdx | 16 +++++++-------- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/docs/content/docs/v4/api-reference/workflow-api/get-hook-by-token.mdx b/docs/content/docs/v4/api-reference/workflow-api/get-hook-by-token.mdx index 1448c48f3e..0e18d23faf 100644 --- a/docs/content/docs/v4/api-reference/workflow-api/get-hook-by-token.mdx +++ b/docs/content/docs/v4/api-reference/workflow-api/get-hook-by-token.mdx @@ -122,7 +122,11 @@ import { getHookByToken, resumeHook } from "workflow/api"; export async function POST(request: Request) { const { token, data } = await request.json(); - const expectedEnv = process.env.VERCEL_ENV || "development"; + // On Vercel the platform sets these: `VERCEL_TARGET_ENV` carries a custom + // environment's slug, where `VERCEL_ENV` reports `preview`. Running against + // the Local World, hooks are recorded as `local`. + const expectedEnv = + process.env.VERCEL_TARGET_ENV ?? process.env.VERCEL_ENV ?? "local"; try { const hook = await getHookByToken(token); // [!code highlight] diff --git a/docs/content/docs/v5/api-reference/workflow-api/get-hook-by-token.mdx b/docs/content/docs/v5/api-reference/workflow-api/get-hook-by-token.mdx index 82638ae989..0c4aee6cfc 100644 --- a/docs/content/docs/v5/api-reference/workflow-api/get-hook-by-token.mdx +++ b/docs/content/docs/v5/api-reference/workflow-api/get-hook-by-token.mdx @@ -124,7 +124,11 @@ import { getHookByToken, resumeHook } from "workflow/api"; export async function POST(request: Request) { const { token, data } = await request.json(); - const expectedEnv = process.env.VERCEL_ENV || "development"; + // On Vercel the platform sets these: `VERCEL_TARGET_ENV` carries a custom + // environment's slug, where `VERCEL_ENV` reports `preview`. Running against + // the Local World, hooks are recorded as `local`. + const expectedEnv = + process.env.VERCEL_TARGET_ENV ?? process.env.VERCEL_ENV ?? "local"; try { const hook = await getHookByToken(token); // [!code highlight] diff --git a/docs/content/docs/v5/configuration/cli-and-web-ui.mdx b/docs/content/docs/v5/configuration/cli-and-web-ui.mdx index acb5c577ea..4ca56d59fe 100644 --- a/docs/content/docs/v5/configuration/cli-and-web-ui.mdx +++ b/docs/content/docs/v5/configuration/cli-and-web-ui.mdx @@ -51,7 +51,7 @@ Whichever way they are resolved, the CLI ends up building a Vercel World from an - Environment variable: `WORKFLOW_VERCEL_ENV` - Default: `production` - Vercel environment for `--backend vercel`. -- Accepts `production` or `preview`. +- Accepts `production`, `preview`, or a custom environment's slug. ## Web UI diff --git a/docs/content/worlds/v4/vercel.mdx b/docs/content/worlds/v4/vercel.mdx index 74c0f06649..83c36c42d6 100644 --- a/docs/content/worlds/v4/vercel.mdx +++ b/docs/content/worlds/v4/vercel.mdx @@ -28,7 +28,7 @@ That's it. Vercel automatically: - Selects the Vercel World backend - Configures authentication using OIDC tokens - Provisions storage and queuing infrastructure -- Isolates data per environment (production, preview, development) +- Isolates data per environment (production, preview, and any custom environments) @@ -98,12 +98,12 @@ Inside a Vercel deployment there is nothing to authenticate: the platform inject Do not reuse the `VERCEL_OIDC_TOKEN` that `vercel env pull` writes into - `.env.local` as your Workflow credential. That token is scoped to your - project's **development** environment, and the backend attributes every - request to the environment in the token's claim — so it addresses - development data, not your `production` or `preview` runs, and it should not - be used to reach across that boundary. A pulled OIDC token is still the right - thing for [bypassing deployment + `.env.local` as your Workflow credential. It is a local artifact of your + linked project, not a credential for the Workflow API: requests made with it + are attributed to the environment carried in the token itself, not to the + `production` or `preview` data you mean to reach. Authenticate with a Vercel + auth token instead. A pulled OIDC token is still the right thing for + [bypassing deployment protection](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources) when your script has to fetch a protected preview URL; that is a separate credential from the one that talks to the Workflow API. @@ -118,7 +118,7 @@ These four values are what an external client needs. The names are the conventio | `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | | `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required. Set it together with the token and the project ID. | | `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required. Set it together with the token and the team ID. | -| `WORKFLOW_VERCEL_ENV` | The environment to target | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env) for the accepted values. | +| `WORKFLOW_VERCEL_ENV` | `production`, `preview`, or a custom environment's slug | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env). | Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page. @@ -128,6 +128,8 @@ Three more are situational: - `WORKFLOW_VERCEL_PROJECT_NAME` — the project slug, used only to build dashboard links. - `VERCEL_TOKEN` — a fallback the World reads for the Vercel API calls it makes alongside your Workflow requests (fetching a run's encryption key, resolving `deploymentId: "latest"`). A client that passes `token` explicitly, as below, already covers those calls and does not need it. +For a custom environment, set `WORKFLOW_VERCEL_ENV` to the environment's **slug**, not its ID. + The Workflow SDK does not read `WORKFLOW_VERCEL_*` from the environment on your behalf — the runtime's own `createWorld()` deliberately ignores them, so @@ -233,7 +235,7 @@ The `WORKFLOW_VERCEL_*` variables below configure tooling that talks to a Vercel ### `WORKFLOW_VERCEL_ENV` -The Vercel environment to use. Options: `production`, `preview`, `development`. Automatically detected. +The Vercel environment to use. Options: `production`, `preview`, or a custom environment's slug. Default: `production`. See [Environment variables](#environment-variables) for how a custom environment is resolved. ### `WORKFLOW_VERCEL_AUTH_TOKEN` diff --git a/docs/content/worlds/v5/vercel.mdx b/docs/content/worlds/v5/vercel.mdx index 620d245bb7..3056106f42 100644 --- a/docs/content/worlds/v5/vercel.mdx +++ b/docs/content/worlds/v5/vercel.mdx @@ -28,7 +28,7 @@ That's it. Vercel automatically: - Selects the Vercel World backend - Configures authentication using OIDC tokens - Provisions storage and queuing infrastructure -- Isolates data per environment (production, preview, development) +- Isolates data per environment (production, preview, and any custom environments) @@ -134,12 +134,12 @@ Inside a Vercel deployment there is nothing to authenticate: the platform inject Do not reuse the `VERCEL_OIDC_TOKEN` that `vercel env pull` writes into - `.env.local` as your Workflow credential. That token is scoped to your - project's **development** environment, and the backend attributes every - request to the environment in the token's claim — so it addresses - development data, not your `production` or `preview` runs, and it should not - be used to reach across that boundary. A pulled OIDC token is still the right - thing for [bypassing deployment + `.env.local` as your Workflow credential. It is a local artifact of your + linked project, not a credential for the Workflow API: requests made with it + are attributed to the environment carried in the token itself, not to the + `production` or `preview` data you mean to reach. Authenticate with a Vercel + auth token instead. A pulled OIDC token is still the right thing for + [bypassing deployment protection](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources) when your script has to fetch a protected preview URL; that is a separate credential from the one that talks to the Workflow API. @@ -154,7 +154,7 @@ These four values are what an external client needs. The names are the conventio | `WORKFLOW_VERCEL_AUTH_TOKEN` | Vercel auth token | Keep it secret. Create one under [account settings → tokens](https://vercel.com/account/tokens), scoped to the team that owns the project. | | `WORKFLOW_VERCEL_TEAM` | Vercel team ID (`team_...`) | Required. Set it together with the token and the project ID. | | `WORKFLOW_VERCEL_PROJECT` | Vercel project ID (`prj_...`) | Required. Set it together with the token and the team ID. | -| `WORKFLOW_VERCEL_ENV` | The environment to target | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env) for the accepted values. | +| `WORKFLOW_VERCEL_ENV` | `production`, `preview`, or a custom environment's slug | Defaults to `production`. See [`WORKFLOW_VERCEL_ENV`](#workflow_vercel_env). | Both IDs are in `.vercel/project.json` after `vercel link` — `projectId` is the project, `orgId` the team or user that owns it — or on the project's settings page.