# Authentication

Use your API key from your server. Give a browser a short-lived client secret instead, so the key never leaves your server.

## API keys

Signup issues an API key, `wk_…`, shown once. Create more keys, and revoke them, in the [console](https://api.dotwave.ai/dashboard/api-keys). The key authenticates every REST call and every WebSocket.

Send it as a bearer token:

```bash
curl https://api.dotwave.ai/v1/models \
  -H "Authorization: Bearer $DOTWAVE_API_KEY"
```

REST calls also accept the key in an `X-API-Key` header.

## On each WebSocket

| API | From your server | From a browser |
| --- | --- | --- |
| [Live API](https://dotwave.ai/docs/live/websocket/) | `Authorization: Bearer <API key>` | `?token=<client secret>`, from `POST /v1/live/client_secrets` |
| [Realtime API](https://dotwave.ai/docs/realtime/websocket/) | `Authorization: Bearer <API key>` | `?token=<client secret>`, from `POST /v1/realtime/client_secrets` |
| [Deepgram-compatible API](https://dotwave.ai/docs/deepgram/) | `Authorization: Token <API key>` | `?token=<client secret>`, from `POST /v1/realtime/client_secrets` |

A client that can set headers may send a client secret in the `Authorization` header instead. An API key is never accepted in the query string.

## Client secrets

A client secret opens one session. Create it on your server with your API key and hand only its value to the browser. It carries the session’s configuration and expires after `expires_after.seconds`: 60 by default, 600 at most. An unused secret counts toward your concurrency limit until it expires, so create it just before the browser connects.

- **Live API**: `POST https://api.dotwave.ai/v1/live/client_secrets`. See [Live API client secrets](https://dotwave.ai/docs/live/client-secrets/).
- **Realtime and Deepgram-compatible APIs**: `POST https://api.dotwave.ai/v1/realtime/client_secrets`. See [Realtime API client secrets](https://dotwave.ai/docs/realtime/client-secrets/).

The response carries the secret as `value`, its expiry as `expires_at`, and the socket to open as `session.live_url`. Browsers cannot set headers on a WebSocket, so they pass the secret as the `token` query parameter:

```typescript
const ws = new WebSocket(`${liveUrl}?token=${encodeURIComponent(secret)}`);
```

## Keep keys safe

- **Servers only**: Never put an API key in browser code, a mobile app or a public repository.
- **One key per use**: Create a key for each service or environment, so you can revoke one without stopping the others.
- **Revoke**: Revoke a key you no longer trust in the console. Requests with it then fail with `key_revoked`.
