# Authentication

> API tokens, what each scope allows, and the endpoints that make, list and revoke tokens.

Every request carries an **API token**. A token belongs to one cluster, and can be limited to some of its spaces and some **scopes**, the kinds of call it may make.

```bash
curl -s "$EMIT_URL/v1/spaces/orders/jobs?limit=10" -H "Authorization: Bearer $EMIT_TOKEN"
```

emit also takes the token in an `x-api-key` header. Never put an API token in a URL. To let a browser follow a job, give it a [follow token](/docs/guides/browser-progress) instead.

A missing or unknown token gets `401`. A token without the scope, or not for the space, gets `403`.

## Scopes

Scopes are lower case in the dashboard and the SDKs, and upper case in the API: `CREATE`, `WORK`, `READ`, `ADMIN`.

| Scope | Allows |
| --- | --- |
| `create` | Creating jobs, starting workflow runs, and sending signals to runs. |
| `work` | Claiming jobs and reporting on them. Workflow workers also need `read`. |
| `read` | Reading jobs, their log lines, counts and space settings; following jobs; making follow tokens. |
| `admin` | Everything, plus cancelling jobs, changing space settings, listing spaces, and managing tokens. |

Give each part of your system only what it needs. An API server that queues jobs needs `create`, and `read` if it waits for results. A worker needs `work`, a workflow worker `work` and `read`, plus `create` if it sends signals. Keep `admin` tokens for scripts and deploys that manage spaces.

## Making a token

In the dashboard, open the cluster's **Tokens** tab and create one. Choose its name, its spaces (blank for every space) and its scopes. Its secret is shown once, with a `.env` block holding the endpoint and the token.

With an `admin` token, you can make tokens through the API too.

### Create a token

`POST /v1/tokens` · scope `admin`

:::fields
:::field name {type="string" required}
A name to recognise it by, like `api-server`.
:::
:::field scopes {type="string[]" required}
Any of `CREATE`, `WORK`, `READ` and `ADMIN`.
:::
:::field spaces {type="string[]"}
The spaces it's for. Leave it out, or empty, for every space.
:::
:::

:::code-group
```bash curl
curl -s "$EMIT_URL/v1/tokens" \
  -H "Authorization: Bearer $EMIT_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "api-server", "spaces": ["orders"], "scopes": ["CREATE", "READ"]}'
```
```ts TypeScript
const token = await admin.tokens.create({ name: "api-server", spaces: ["orders"], scopes: ["create", "read"] });
```
```go Go
token, err := admin.Tokens.Create(ctx, emit.Token{Name: "api-server", Spaces: []string{"orders"}, Scopes: []emit.Scope{emit.ScopeCreate, emit.ScopeRead}})
```
:::

```json
{
  "id": "01M3QSGMZQ02BZ0F84Q8VQZG00",
  "name": "api-server",
  "spaces": ["orders"],
  "scopes": ["CREATE", "READ"],
  "createdAtMs": "1790726132727",
  "secret": "emit_56c1edad7502dc19d7178442cddd0ddaa1dd8ae8e31ec0a9b0dcbaebcdb2f7df"
}
```

`secret` is in this answer only. emit keeps a hash of it, so a lost secret can't be shown again: make a new token.

### List tokens

`GET /v1/tokens` · scope `admin`

```bash
curl -s "$EMIT_URL/v1/tokens" -H "Authorization: Bearer $EMIT_ADMIN_TOKEN"
```

Answers `{"tokens": [...]}`, each as above with an empty `secret`.

### Revoke a token

`DELETE /v1/tokens/{id}` · scope `admin`

```bash
curl -s -X DELETE "$EMIT_URL/v1/tokens/01M3QSGMZQ02BZ0F84Q8VQZG00" -H "Authorization: Bearer $EMIT_ADMIN_TOKEN"
```

A revoked token stops working within seconds.
