# Access tokens

> Mint, use and revoke the project scoped token that CI, agents and direct API calls present to extension.dev.

Canonical: https://docs.extension.dev/tools/access-tokens
This page as Markdown: https://docs.extension.dev/tools/access-tokens.md
Section: Agent tools and the CLI
Siblings: [MCP server](https://docs.extension.dev/tools/mcp.md) · [Server flags and telemetry](https://docs.extension.dev/tools/mcp/flags.md) · [Platform tools](https://docs.extension.dev/tools/mcp/platform.md) · [Run on Safari](https://docs.extension.dev/tools/mcp/safari.md) · [Agent skill](https://docs.extension.dev/tools/skill.md) · [Pair with Chrome DevTools MCP](https://docs.extension.dev/tools/chrome-devtools-mcp.md) · [Publish a build](https://docs.extension.dev/tools/publish.md) · [CLI](https://docs.extension.dev/tools/cli.md) · [Agent bridge](https://docs.extension.dev/tools/agent-bridge.md) · [Agent logs](https://docs.extension.dev/tools/logs.md) · [Inspect and act](https://docs.extension.dev/tools/inspect-and-act.md)
Map of every page: https://docs.extension.dev/llms.txt · Whole corpus: https://docs.extension.dev/llms-full.txt

---

# 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

```bash
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.

```bash
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](/tools/mcp) reads the same variable. A build reads its own
environment values the same way. See the
[Content Env template](https://templates.extension.dev/content-env) 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`](/automate/api#token-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 the `Authorization` header. 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.

## Next

- [CLI](/tools/cli)
- [MCP server](/tools/mcp)
- [HTTP API](/automate/api)
