Access tokens
An access token authenticates a machine against one project. Reach for one when an agent session, a CI pipeline or a direct API call needs to act on your project without a browser sign-in.
Quick start
npx @extension.dev/mcp login --project <workspace>/<project>The command prints a user code and the address extension.dev/device. Approve the code there in a browser signed in to extension.dev, and a project scoped token lands in your local token store.
How it works
The project is the scope. A token acts on the one project it was minted for. It cannot read another project in the same workspace, and the TOKEN_PROJECT_MISMATCH refusal names that boundary.
Two doors mint a token. The console does it in project settings, at console.extension.dev/<workspace>/<project>/project-settings/access-tokens. The device flow does it without a pasted secret: the extension_auth MCP tool with action: "login", or the package bin above, prints a code that you approve at extension.dev/device. That address lands on www.extension.dev/device. GitHub is federated server side, so no GitHub token reaches your machine.
A token travels in the Authorization header only. The platform API refuses a token in a query string with the TOKEN_IN_QUERY error. Registry reads at registry.extension.land are the one exception: they accept a ?t= token in the URL, and the mint response shows that usage. Treat a registry URL that carries a token as the credential it contains.
export EXTENSION_DEV_TOKEN=...
curl -s -X POST https://www.extension.dev/api/cli/stores/submit \
-H "Authorization: Bearer $EXTENSION_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"browsers":["chrome"],"buildSha":"<sha>","channel":"stable"}'The MCP server reads the same variable. A build reads its own environment values the same way. See the Content Env template for the pattern.
Lifetime
One to seven days, chosen at mint time. The default and the maximum are both seven, and the server enforces the cap: a token that leaks stays useful for days, not forever.
WARNING
A seven day maximum means a pipeline with a stored token starts failing within a week. That is the point, and it is the cost of the cap: a pipeline that calls the platform API needs its secret rotated on that cadence. POST /api/oidc/exchange mints a one hour token per run and stores nothing, but that token reads the registry only. It does not authenticate the platform API.
Revocation
Revoke from the Access tokens page or with DELETE /api/projects/{id}/access-tokens/{tokenId}. Revocation writes to the record the platform keeps about your token, and two paths read that record differently.
The project API, which builds, releases and store submissions use, checks the record on every request and fails closed. A revoked token is refused with TOKEN_REVOKED, and a token whose record cannot be read is refused with TOKEN_NOT_TRACKED.
The artifact sharing path refuses a token only on an explicit revocation record. When the record store cannot be read, the request proceeds on the strength of the signature, so an outage in one store does not stop sharing everywhere. The registry worker caches the revocation list for 60 seconds, so a fresh revocation takes about a minute to reach it. A leaked token is not dead there the moment you click revoke.
Logging out locally does not revoke the token. extension_auth with action: "logout" removes the local copy and says so. If a token may have leaked, revoke it from the console and treat the next minute as exposed on the sharing path.
Reference
| Fact | Value |
|---|---|
| Scope | One project |
| Lifetime | 1 to 7 days, default 7, clamped by the server |
| Transport | Authorization: Bearer <token> |
| Registry exception | ?t=<token> on registry.extension.land reads |
| Device code validity | 15 minutes |
| Revocation reach, project API | Every request |
| Revocation reach, sharing path | About 60 seconds |
| Local store | ~/.config/extension-dev/auth.json, $XDG_CONFIG_HOME honored, %APPDATA%\extension-dev\auth.json on Windows |
| Code | Status | Meaning |
|---|---|---|
BAD_TOKEN | 401 | The token is invalid |
TOKEN_IN_QUERY | 401 | The token was sent as ?token= instead of the header |
TOKEN_NOT_TRACKED | 401 | The platform has no record of the token |
TOKEN_REVOKED | 401 | The token was revoked |
SHARE_TOKEN | 403 | A share link token was presented where a project token is required |
TOKEN_PROJECT_MISMATCH | 403 | The token belongs to another project |
A token never reads store credentials, which the platform holds and never returns, and never reads another project.
When it fails
- A pipeline starts failing after a week: the token expired. Mint a new one and rotate the secret. There is no longer option.
TOKEN_IN_QUERY: move the token into theAuthorizationheader. Only registry reads accept?t=.TOKEN_PROJECT_MISMATCH: the token was minted for another project. Mint one in the right project's settings.SHARE_TOKEN: you pasted a?share=link token. Mint a project token instead.- A revoked token still reads a shared artifact: the sharing path caches revocations for about a minute. Wait it out.
- The OIDC exchange token is refused by the platform API: that token reads the registry only. Use a stored project token for API calls.