> ## Documentation Index
> Fetch the complete documentation index at: https://assemblyai.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Temporary tokens

> Mint a short-lived token on your server so a browser or app can use an AssemblyAI product without your API key, and narrow what the token can do.

A temporary token lets a browser or an app use an AssemblyAI product without your API key. Your server mints the token with your key, hands the token to the client, and the client uses it in place of the key. Each token expires after a short window, usage through it is attributed to the key that minted it, and a token cannot mint another token.

Keep your API key on your server. Anything a client receives, including a token, can be read by that client, so keep each token's window short and mint a new one for each connection.

## Mint a token

Send `POST` to a `token.` host with your API key in the `Authorization` header and a JSON body. Only the `token.` hosts accept this request:

| Host | Serves |
| - | - |
| `https://token.assemblyai.com/v1/token` | Nearest region, either zone |
| `https://token.us.assemblyai.com/v1/token` | US data zone |
| `https://token.eu.assemblyai.com/v1/token` | EU data zone |

Mint on the host of the zone your sessions connect to.

```bash theme={null}
curl -X POST "https://token.assemblyai.com/v1/token" \
  -H "Authorization: $ASSEMBLYAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product": "streaming", "expires_in_seconds": 60}'
```

The response is JSON with the token in its `token` field:

```json theme={null}
{ "token": "<temporary token>", "expires_in_seconds": 60 }
```

### Use the token

Pass the token in place of your API key. A server can send it in the `Authorization` header, as it would your key. A browser cannot set headers on a WebSocket handshake, so it passes the token in the `token` query parameter of the WebSocket URL instead. Each product page shows the exact WebSocket URL and parameters.

Mint a fresh token for each connection. Until its window closes, a token can open more than one session, so keep `expires_in_seconds` short.

### Request fields

| Field | Required | Description |
| - | - | - |
| `product` | Yes | The product the token is minted for. See the list below. |
| `expires_in_seconds` | Yes | How long the token can wait before it opens a session, from 1 to 600. |
| `max_session_duration_seconds` | No | Caps the session opened with the token. At least 60. |
| `access` | No | Settings per product that narrow what the token can open. See [Limit what a token can do](#limit-what-a-token-can-do). |

### Products

| Product | Aliases |
| - | - |
| `streaming` | `realtime` |
| `speech_to_speech` | `voice_agent`, `agent` |
| `sync` | |
| `dictation` | |
| `tts` | |
| `llm_gateway` | |

Each product page shows how its client uses the token:

* [Real-time STT](https://assemblyai.com/docs/streaming/getting-started/transcribe-streaming-audio.md)
* [Streaming TTS](https://assemblyai.com/docs/tts/authenticate-with-a-temporary-token.md)
* [Voice Agent API](https://assemblyai.com/docs/voice-agents/voice-agent-api/browser-integration.md)

## Limit what a token can do

A token can open any product your account can, unless you narrow it. The `access` field on the request narrows it for that one token. Your API key and account are not changed, and a token minted without `access` behaves as it does without it.

Each product in `access` is keyed by product name or alias and takes:

| Field | Default | Description |
| - | - | - |
| `enabled` | `true` | Set to `false` to stop the token from opening that product. |
| `models` | Not set | Limits per model. Only `llm_gateway` takes it. |

Each model in `models` takes these optional limits. Each one is a whole number of 1 or more.

| Limit | Meaning |
| - | - |
| `rpm` | Requests per minute made with this token. |
| `tpm` | Tokens per minute made with this token. |
| `max_tokens` | The largest `max_tokens` a request may set. |
| `max_prompt_size` | The longest prompt, in characters. |

Products you leave out of `access` are not restricted by the token. List each product once: `streaming` and `realtime` name the same product, so listing both is an error.

### Defaults for LLM Gateway limits

| Case | Result |
| - | - |
| A model you do not list under `models` | 10 requests per minute. Prompt size and `max_tokens` are not checked. |
| A listed model with `rpm` unset | 10 requests per minute. |
| A listed model with `tpm` unset | Derived from `rpm`. |
| A listed model with `max_tokens` unset | The gateway's default `max_tokens`. |
| A listed model with `max_prompt_size` unset | 5000 characters. |

## Tutorials

### 1. Open one product only

A token for a real-time transcription page should not open text-to-speech, a voice agent session, or the LLM Gateway. Mint it with those products disabled:

```bash theme={null}
curl -X POST "https://token.assemblyai.com/v1/token" \
  -H "Authorization: $ASSEMBLYAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "product": "streaming",
    "expires_in_seconds": 60,
    "access": {
      "tts": { "enabled": false },
      "speech_to_speech": { "enabled": false },
      "llm_gateway": { "enabled": false }
    }
  }'
```

### 2. Limit one LLM Gateway model

To let a chat widget use one model at a set rate, mint an `llm_gateway` token with limits for that model. Other models are not listed, so they get the default of 10 requests per minute:

```python Python theme={null}
import os

import requests

response = requests.post(
    "https://token.assemblyai.com/v1/token",
    headers={"Authorization": os.environ["ASSEMBLYAI_API_KEY"]},
    json={
        "product": "llm_gateway",
        "expires_in_seconds": 120,
        "access": {
            "llm_gateway": {
                "enabled": True,
                "models": {
                    "claude-sonnet-4-6": {
                        "rpm": 20,
                        "tpm": 40000,
                        "max_tokens": 500,
                        "max_prompt_size": 8000,
                    }
                },
            }
        },
    },
)
response.raise_for_status()
token = response.json()["token"]
```

```javascript JavaScript theme={null}
const response = await fetch("https://token.assemblyai.com/v1/token", {
  method: "POST",
  headers: {
    Authorization: process.env.ASSEMBLYAI_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    product: "llm_gateway",
    expires_in_seconds: 120,
    access: {
      llm_gateway: {
        enabled: true,
        models: {
          "claude-sonnet-4-6": {
            rpm: 20,
            tpm: 40000,
            max_tokens: 500,
            max_prompt_size: 8000,
          },
        },
      },
    },
  }),
});
if (!response.ok) throw new Error(`token request failed: ${response.status}`);
const { token } = await response.json();
```

A request that sets `max_tokens` above 500, or a prompt longer than 8000 characters, is refused by the gateway. The same token can still call other models, at the default of 10 requests per minute.

### 3. Handle a rejected request

The API checks the whole request before it mints anything and returns every problem at once. A problem with the request body returns `400` with the problems in `detail`:

```json theme={null}
{
  "detail": [
    "product 'tts' does not take model limits, only 'llm_gateway' does"
  ]
}
```

Unknown fields inside `access`, such as a misspelled `enabled` or a limit that is not one of the four above, return `422` with the field named in the error. A `401` or `403` means the API key was not accepted. A `500` or `502` means the token could not be issued right now; retry with backoff.
