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

# API key authentication

> Authenticate server-side requests directly with your API key.

API key authentication sends your key directly in the `Authorization` header. Use it to call Inworld APIs from your servers.

<Warning>The API key is for server-side code only. Never use it in browsers, mobile apps, or game builds. Create a [token](https://docs.inworld.ai/portal/authentication.md) for those instead. See [Security best practices](https://docs.inworld.ai/portal/auth-security.md).</Warning>

## The header

Use the **Base64 credentials** you copy from [Inworld Portal](https://platform.inworld.ai/api-keys) or the [Inworld CLI](https://docs.inworld.ai/developer-tools/inworld-cli.md) as-is:

```
Authorization: Basic $INWORLD_API_KEY
```

`INWORLD_API_KEY` is the complete encoded credential, without the `Basic ` prefix. Portal's **Base64** copy action returns `base64(key_id:secret)`. The CLI's `inworld auth print-api-key` prints that same representation for the selected key (or echoes `INWORLD_API_KEY` if it is already set). Neither output needs another encoding step.

- The **key ID** identifies the key; it cannot authenticate by itself.
- The **secret** proves ownership. If an SDK explicitly asks for separate key and secret fields, supply those separate values.
- The **Base64 credential** combines the ID and secret. Encoding is reversible. Treat the whole string as a secret.
- The CLI’s `inworld auth print-access-token` returns its signed-in user access token. This differs from `print-api-key` and the client tokens created below. Do not use it in these runtime examples.
- A **minted token** is a separate credential returned by a token endpoint as `accessToken` or `token`. Use it with Bearer. Do not Base64-encode it.

Only when you have separate ID and secret values, construct the combined credential once on your server:

```javascript
const apiKey = Buffer.from(`${keyId}:${secret}`, 'utf8').toString('base64');
```

Do not encode a copied Base64 credential again, use a key ID alone, or pass the combined credential to a username/password helper that encodes it again.

For example, after setting `INWORLD_API_KEY` to the copied Base64 value:

```bash
curl https://api.inworld.ai/tts/v1/voices \
  --header "Authorization: Basic $INWORLD_API_KEY"
```

## Basic versus Bearer

Use `Authorization: Basic $INWORLD_API_KEY` for direct server requests. Keep the copied credential unchanged and on your server. For SDK requests, follow the complete [OpenAI SDK](https://docs.inworld.ai/router/openai-compatibility.md#openai-sdk) or [Anthropic SDK](https://docs.inworld.ai/router/anthropic-compatibility.md#anthropic-sdk) configuration.

Use Bearer for a [client token created by your backend](https://docs.inworld.ai/portal/authentication.md#pick-a-credential). Changing an API key's prefix to Bearer does not turn it into a short-lived token or make it safe for client code.

## Where it works

The documented platform endpoints accept direct API key authentication, subject to the key’s permissions: TTS, STT, LLM, Voices, Realtime, and the [one-time token](https://docs.inworld.ai/portal/ephemeral-tokens.md) mint. WebSocket endpoints accept the same header on the connection request. The one exception is the [session token](https://docs.inworld.ai/portal/session-tokens.md) mint, which authenticates with a signed request instead of the plain header.

Store the key in an environment variable or secret manager, and read it at runtime. See [Security best practices](https://docs.inworld.ai/portal/auth-security.md) for handling rules and what to do if a key leaks.

## Recover from authentication failures

Read the response body as well as the HTTP status. Invalid keys can return **403**. A **403** can also mean a valid credential lacks permission.

| Failure | What to do |
| --- | --- |
| Missing or malformed authorization; invalid credentials; key deleted or not found (401 or 403) | Check that the environment variable is nonempty in the process making the request. Copy the full Base64 credential again, remove quotes/whitespace accidentally included in its value, and send exactly one `Basic ` prefix. Check the key is active and belongs to the intended environment. Do not double encode. |
| Invalid, expired, or already-used token | Obtain a fresh token from your backend. Mint a new one-time token for every request or connection attempt, including retries after a later request failure. |
| Required scopes or workspace access denied (403) | Check the selected workspace and key type. A Realtime-only key cannot call TTS or Router directly. Use a Standard key for those APIs; use a key with access to the workspace containing the requested resource. |
| Voice or Router write operation denied (403) | Enable the relevant [write permission](https://docs.inworld.ai/portal/api-keys.md#key-permissions). Calling TTS or chat completions does not require these write toggles; creating a router or cloning a voice does. Mint a fresh token after changing permissions. |
| Token mint refuses Bearer (403) | Call `/auth/v1/tokens` from your backend with the Base64 API credential under Basic, even if Bearer works for chat completions. |
| Single-use token cannot open Realtime (403) | Use a backend-minted [session token](https://docs.inworld.ai/portal/session-tokens.md), or connect through a server proxy using Basic. |
| Session-token signature rejected | Check the server's UTC clock, key ID/secret pair, signed host and method. Generate a fresh timestamp, nonce, and signature for each attempt; use the [session mint example](https://docs.inworld.ai/portal/session-tokens.md#mint-a-session-token). |
| Credit/plan failure (402), rate/concurrency limit (429), or server error (5xx) | Follow the response's billing, limit, or retry guidance. These statuses do not establish that your key is invalid. Back off on retryable failures; obtain a fresh one-time token for the next attempt. |

If the problem persists, share the endpoint, status, redacted error message, and request ID with support. Never include the authorization header or token.
