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

# Authentication

> API key format, token handling, and environment separation.

All API requests must include a bearer token in the `Authorization` header:

```bash theme={null}
Authorization: Bearer xe_...
```

Xenovia API keys are prefixed with `xe_`. The runtime validates the prefix before attempting key lookup — requests with malformed or unprefixed tokens are rejected immediately.

<Callout type="warn">
  Never send API keys from browser clients or include them in client-side bundles. All requests must originate server-side.
</Callout>

## Token scopes

Each `xe_...` key is scoped to one or more proxies and an environment. The key resolves an HMAC-signed identity blob containing the `proxy_id` and `org_id`. A key issued for proxy `abc` cannot be used to access proxy `xyz`.

Tokens used with the runtime proxy are also validated against the `X-Xenovia-Agent-Path` header — the path segment in the proxy URL must match the resolved proxy ID. This prevents cross-proxy key misuse.

## Token handling

* Issue tokens through the Xenovia platform under **Settings → API Keys**.
* Store tokens in a secret manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, etc.).
* Inject at request time from the secret manager — never hardcode in source.
* Rotate on a scheduled cadence (recommended: 90 days) and immediately after any suspected exposure or team member offboarding.

## Environment separation

Use separate tokens per environment. Never reuse production credentials in non-production systems.

```bash theme={null}
# .env.dev
XENOVIA_API_KEY=xe_dev_...

# .env.prod
XENOVIA_API_KEY=xe_prod_...
```

## Service-to-service pattern

```python theme={null}
import os
import boto3  # or your secret manager client

def get_api_key() -> str:
    client = boto3.client("secretsmanager")
    secret = client.get_secret_value(SecretId="xenovia/api-key")
    return secret["SecretString"]

api_key = get_api_key()

# Use the key server-side only
headers = {"Authorization": f"Bearer {api_key}"}
```

## Authentication failures

| Status             | Meaning                                                                  |
| ------------------ | ------------------------------------------------------------------------ |
| `401 Unauthorized` | Key is missing, malformed, or not prefixed with `xe_`                    |
| `403 Forbidden`    | Key is valid but does not have access to the requested proxy or resource |

## Rate limiting

See [Errors and Limits](/api-reference/errors-and-limits) for rate limit behavior and retry guidance.
