> ## 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.

# Authenticate with a temporary token

> Mint a short-lived token on your server so a browser or an app can open a Streaming TTS session without your API key.

A page or an app that talks to the Streaming TTS API directly must not carry your API key: the key would be in the shipped code, in URLs, and in whatever logs those URLs reach. Mint a temporary token on your server instead, hand it to the client, and let the client open its session with that. The token is good for one short window, usage through it is attributed to the key that minted it, and a token cannot mint another token.

Browsers cannot set headers on a WebSocket handshake, which is why the API also accepts the credential as a `token` query parameter. That parameter takes an API key too, but never send one from a device you do not control.

**1. Mint a token on your server**

`GET https://token.assemblyai.com/v1/token` with your API key in the `Authorization` header and two query parameters, both required: `product=tts`, and `expires_in_seconds`, how long the token can wait before it opens a session.

**Python**

```python theme={null}
import os

import requests


def create_temporary_token():
    response = requests.get(
        "https://token.assemblyai.com/v1/token",
        params={"product": "tts", "expires_in_seconds": 60},
        headers={"Authorization": os.environ["ASSEMBLYAI_API_KEY"]},
    )
    response.raise_for_status()
    return response.json()["token"]
```

The Python SDK has no minting helper for Streaming TTS; the request above is all it takes.

**JavaScript**

```javascript theme={null}
async function createTemporaryToken() {
  const url = new URL("https://token.assemblyai.com/v1/token");
  url.search = new URLSearchParams({ product: "tts", expires_in_seconds: "60" }).toString();

  const response = await fetch(url, {
    headers: { Authorization: process.env.ASSEMBLYAI_API_KEY },
  });
  if (!response.ok) throw new Error(`token request failed: ${response.status}`);

  const data = await response.json();
  return data.token;
}
```

**JavaScript SDK**

```javascript theme={null}
import { AssemblyAI } from "assemblyai";

const client = new AssemblyAI({ apiKey: process.env.ASSEMBLYAI_API_KEY });

async function createTemporaryToken() {
  return client.tts.createTemporaryToken({ expires_in_seconds: 60 });
}
```

The SDK mints on the token host of the zone its `ttsBaseUrl` is in, so a client set up for the EU host gets an EU token.

**cURL**

```bash theme={null}
curl "https://token.assemblyai.com/v1/token?product=tts&expires_in_seconds=60" \
  -H "Authorization: $ASSEMBLYAI_API_KEY"
```

The answer is JSON with the token in its `token` field, a long opaque string:

```json theme={null}
{ "token": "AQICAHg...", "expires_in_seconds": 60 }
```

* **`expires_in_seconds`**, 1 to 600, is the window in which the token can open a session. Past it the connection is refused with `1008` and `Unauthorized: Signature has expired`. A session that is already open is not affected when its token expires.
* **`max_session_duration_seconds`**, optional and at least 60, caps the session opened with the token. The cap shows up as `expires_at` on the `Begin` frame, and the session closes with code `3008` when it is reached. A session never lasts longer than an hour either way.
* **Zones.** `token.us.assemblyai.com` and `token.eu.assemblyai.com` serve the same route within the US and EU data zones, and a token from either opens sessions on both TTS hosts. Mint on the token host of the zone your sessions connect to, so that the minting request is processed in that zone too; see [Cloud endpoints and data residency](https://assemblyai.com/docs/tts/endpoints-and-data-zones.md).
* **Errors.** A key the route refuses, or a request it cannot accept, gets a `4xx` answer with the reason in the JSON `detail` field. A `500` or `502` means the route could not issue a token just then: retry with backoff.

> [!WARNING]
>
> **Mint a new token for every connection, reconnects included, and keep
> `expires_in_seconds` short.** A token is not single-use: until it expires,
> it opens as many sessions and HTTP requests as it is given to, so a token
> that leaks from a page can be used by someone else. Never put your API key in the `token` parameter of a page you
> ship. The query string is part of the URL, and URLs are written to server
> access logs, browser history and developer tools.

**2. Open the session with the token**

The client fetches the token from your server, then passes it in place of the API key. In the fragments below, `fetchToken()` and `fetch_token()` are requests to your own server, and `play()` is whatever takes a chunk of audio in your app.

**Python SDK**

```python theme={null}
import assemblyai as aai

config = aai.TTSConfig(voice_id="49c443a4-8a1d-5f7e-b70b-98bdbd9823c3")  # Audrey

with aai.TTSSession(config, token=fetch_token()) as session:
    for chunk in session.speak("Hello from a device without an API key."):
        play(chunk)
```

Give each connection a token of its own: to reconnect, mint a new token and build a new session with it. The session opens on the US host unless `aai.settings.tts_base_url` names the EU one; see [Cloud endpoints and data residency](https://assemblyai.com/docs/tts/endpoints-and-data-zones.md).

**JavaScript SDK**

```javascript theme={null}
import { TextToSpeechSession } from "assemblyai";

const session = new TextToSpeechSession({
  token: await fetchToken(),
  voiceId: "49c443a4-8a1d-5f7e-b70b-98bdbd9823c3", // Audrey
});
await session.connect();

for await (const chunk of session.speak("Hello from a device without an API key.")) {
  play(chunk);
}
await session.close();

// Later, a new connection takes a new token.
await session.connect({ token: await fetchToken() });
```

Building the session directly, with no client, keeps the API key out of the page. It connects to the US host unless you pass `websocketBaseUrl`.

**JavaScript (browser)**

```javascript theme={null}
const params = new URLSearchParams({
  voice_id: "49c443a4-8a1d-5f7e-b70b-98bdbd9823c3",
  language: "english",
  sample_rate: "24000",
  encoding: "pcm_s16le",
  token: await fetchToken(),
});
const ws = new WebSocket(`wss://tts.us.assemblyai.com/v1/ws?${params}`);
```

Mint a fresh token for every connection attempt, reconnects included.

**Python (WebSocket)**

```python theme={null}
from urllib.parse import urlencode

params = {
    "voice_id": "49c443a4-8a1d-5f7e-b70b-98bdbd9823c3",
    "language": "english",
    "sample_rate": 24000,
    "encoding": "pcm_s16le",
    "token": fetch_token(),
}
url = f"wss://tts.us.assemblyai.com/v1/ws?{urlencode(params)}"
```

A token that was refused, because it expired or was minted for another product, closes the session with `1008` and `error_type` `unauthorized` before `Begin`. See [Error handling](https://assemblyai.com/docs/tts/error-handling.md).

## Where else a token works

A temporary token works wherever an API key does, in the `Authorization` header of the [HTTP API](https://assemblyai.com/docs/tts/api-reference/http-api/overview.md), so a page can call it directly too. Under a token, `max_session_duration_seconds` also caps how long an HTTP request may run. See [HTTP API basics](https://assemblyai.com/docs/tts/api-reference/http-api/overview.md#authentication).

### Speak a whole text over HTTP from a page

A page with a whole text to speak and no need for barge-in can call the HTTP API directly, one `fetch` per text, played as it streams in. Every route answers cross-origin requests from any origin. Send the token in the `Authorization` header like a key; the HTTP API reads the credential from that header only. A token stays valid for any number of requests until it expires, so the page can retry a failed request with the same token.

The client below schedules each piece of audio on one `AudioContext` as it arrives, so playback starts with the first bytes. Create and resume the context in a user gesture, and reuse it for every request. `/tts-token` is a route on your server that returns a freshly minted token as JSON.

```javascript theme={null}
const ctx = new AudioContext({ sampleRate: 24000 });

document.getElementById("speak").onclick = async () => {
  await ctx.resume();
  await speak("Hello there. How can I help?", await getToken());
};

async function getToken() {
  const response = await fetch("/tts-token");
  return (await response.json()).token;
}

async function speak(text, token) {
  const res = await fetch("https://tts.us.assemblyai.com/v1/speech", {
    method: "POST",
    headers: { Authorization: token, "Content-Type": "application/json" },
    body: JSON.stringify({ text }),
  });
  if (!res.ok) {
    const isJson = (res.headers.get("Content-Type") ?? "").startsWith("application/json");
    const detail = isJson ? (await res.json()).code : await res.text();
    throw new Error(`${res.status}: ${detail}`);
  }
  const rate = Number(res.headers.get("X-TTS-Sample-Rate"));
  let at = ctx.currentTime + 0.05;
  let carry = new Uint8Array(0);
  const reader = res.body.getReader();
  for (;;) {
    const { value, done } = await reader.read(); // rejects with a TypeError when the response is cut
    if (done) break;
    const bytes = new Uint8Array(carry.length + value.length);
    bytes.set(carry);
    bytes.set(value, carry.length);
    const whole = bytes.length & ~1; // whole 16-bit samples only
    carry = bytes.slice(whole);
    if (whole === 0) continue;
    const pcm = new Int16Array(bytes.buffer, 0, whole / 2);
    const buffer = ctx.createBuffer(1, pcm.length, rate);
    buffer.copyToChannel(Float32Array.from(pcm, (s) => s / 32768), 0);
    const source = ctx.createBufferSource();
    source.buffer = buffer;
    source.connect(ctx.destination);
    at = Math.max(at, ctx.currentTime);
    source.start(at);
    at += buffer.duration;
  }
}
```

Every response header can be read from a page, errors included, so `X-Request-Id` and the `X-TTS-*` headers are available to your error reporting.

## Related

* [WebSocket API basics](https://assemblyai.com/docs/tts/connect-parameters.md#authentication): the `token` parameter.
* [Optimizing accuracy and latency](https://assemblyai.com/docs/tts/getting-started/optimizing-accuracy-and-latency.md#in-a-browser): minting the next token before the page needs it.
* [Limits](https://assemblyai.com/docs/tts/limits.md): the session duration cap a token can set.
* [Streaming Speech-to-Text temporary tokens](https://assemblyai.com/docs/streaming/authenticate-with-a-temporary-token.md): the same pattern for speech-to-text, on its own token route.
