# Tokens

Mint media tokens that let publishers and viewers connect, scoped to the broadcasts and time you choose.

## Two kinds of credential

- An **account token** calls the management API. Create one under [API Tokens](https://moq.pro/settings/tokens) or with `POST /v1/account/tokens`, choosing a none, read, or write level per permission family and, optionally, the projects it reaches. Each operation in the [API reference](/api) names the permission it needs. MCP clients and other agents sign in as the [agent guide](/agents) describes.
- A **media token** is a JWT that a publisher or viewer presents to the relay, usually as the `jwt` query parameter. A signing key on the project mints it, and it grants nothing in the management API.

The rest of this page is about media tokens. The commands reuse `API`, `MOQ_API_TOKEN`, `PID`, and `KID` from the [quickstart](/quickstart).

## Scope a token

A media token carries four claims:

| Claim | Meaning |
| --- | --- |
| `root` | The path every pattern is relative to, which the client must dial. It defaults to the project id, so clients connect to `https://cdn.moq.pro/<project id>`; a [limited key](#limit-a-key) may default it to a deeper path. |
| `publish` | Patterns the client may publish. |
| `subscribe` | Patterns the client may watch. |
| `exp` | When the token stops working. The API allows up to 30 days, or no expiry if omitted. |

A pattern names exactly what it says: `room/alice.hang` is one broadcast, `room/**` is everything under `room`, and `**` is everything under the root. A token that grants nothing is refused.

Prefer short expiries, and mint a fresh token per viewer or per page load.

## Mint with the API

The API signs with a key it holds, so your server never handles signing material. This grants one viewer `room/alice.hang` for ten minutes:

```sh
EXPIRES=$(($(date +%s) + 600))
BODY=$(jq -nc --argjson exp "$EXPIRES" \
  '{subscribe: ["room/alice.hang"], exp: ($exp | todate)}')
VIEW_TOKEN=$(curl --fail-with-body --silent --show-error "$API/projects/$PID/keys/$KID/token" \
  -H "Authorization: Bearer $MOQ_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: \"tokens-view-$EXPIRES\"" \
  --data "$BODY" | jq -er '.token')
```

The account token needs `keys:write`. Each request needs an `Idempotency-Key`; derive it from the claims, as here, so a retry returns the same token and a new mint gets a new key.

## Mint on your own server

To mint without a request per token, download a symmetric key once and sign locally:

```sh
curl --fail-with-body --silent --show-error "$API/projects/$PID/keys/$KID/private" \
  -H "Authorization: Bearer $MOQ_API_TOKEN" | jq -er '.privateKey' > key.jwk
```

Anyone with `key.jwk` can mint any token for the project, so keep it with your other secrets. Sign with the [`moq auth`](https://doc.moq.dev/bin/relay/auth) command, the [`@moq/auth`](https://doc.moq.dev/lib/js/auth) package, or the [`moq-auth`](https://doc.moq.dev/lib/rs/moq-auth) crate. The dashboard's **Tokens** page writes the code for the claims you pick.

You can also import a key you generated yourself with `POST /v1/projects/{pid}/keys/import`, including an asymmetric one whose private half never leaves your server.

## Limit a key

A key created with `publish` or `subscribe` patterns can only mint tokens inside them, and the API refuses a mint that reaches outside. Give each service the narrowest key it needs:

```sh
curl --fail-with-body --silent --show-error "$API/projects/$PID/keys" \
  -H "Authorization: Bearer $MOQ_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: "tokens-viewer-key-1"' \
  --data '{"algorithm":"HS256","label":"Viewers","subscribe":["**"]}'
```

## Revoke

Disabling a key refuses new connections with any token it signed. Enable it again, or delete it once disabled. The project's **Keys** page does the same.

```sh
curl --fail-with-body --silent --show-error -X POST "$API/projects/$PID/keys/$KID/disable" \
  -H "Authorization: Bearer $MOQ_API_TOKEN"
```

## Public access

Public access lets anyone publish or watch matching paths with no token. This opens every broadcast in the project for watching:

```sh
curl --fail-with-body --silent --show-error -X PATCH "$API/projects/$PID" \
  -H "Authorization: Bearer $MOQ_API_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"public":{"v":1,"subscribe":["**"]}}'
```

Patterns are relative to the project. Send `"public": null` to turn it off, or use the project's **Public access** page.

## Your own auth server

Instead of minting tokens, a project can ask your HTTPS endpoint whether to admit each session. Set it on the project's **Auth endpoint** page or with `PUT /v1/projects/{pid}/auth/endpoint`. The CDN signs every request the way it signs [webhooks](/webhooks), and your endpoint answers with the same grant a token carries, following [upstream's auth contract](https://doc.moq.dev/bin/relay/auth#the-contract). The dashboard page lists what the CDN sends and what it accepts back.
