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

# Authentication

> How to authenticate requests to your self-hosted Vyla API instance.

## Overview

Since Vyla API is self-hosted, you manage your own API keys and authentication. The API supports two authentication methods depending on your use case.

| Method        | Header                         | Use case                                       |
| ------------- | ------------------------------ | ---------------------------------------------- |
| Session token | `X-Session-Token`              | Browser players and client-side apps           |
| API key       | `Authorization` or `X-API-Key` | Server-side integrations and direct API access |

<Note>
  You must set up your own API keys when deploying your Vyla API instance. See [Self-Hosting](/self-hosting) for instructions on creating and managing API keys in your SQLite database.
</Note>

***

## Session Tokens (Browser / Player)

If you are building a browser-based player or client-side application, use session tokens. Your API key never leaves the browser — your server calls `POST /api/auth` with a valid API key to receive a short-lived token, then sends that token down to the client for use on subsequent requests.

### Step 1 — Get a token (server-side)

```javascript theme={null}
const { token } = await fetch('http://localhost:7860/api/auth', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
}).then(r => r.json());
```

`POST /api/auth` requires a valid API key from your self-hosted instance. The token inherits the tier of the key used to obtain it — a `public` key issues a `public`-tier token, a `standard` key issues a `standard`-tier token, and so on.

### Step 2 — Send the token (client-side)

```javascript theme={null}
const res = await fetch('http://localhost:7860/movie?id=550', {
  headers: { 'X-Session-Token': token }
});
```

Tokens are HMAC-signed and verified server-side without shared in-memory state, so they work correctly across all server workers. Tokens expire after 30 minutes. Before they expire, refresh them with `POST /api/auth/refresh` instead of re-issuing from `/api/auth` — this avoids sending your API key to the browser again.

### Step 3 — Refresh the token (client-side)

```javascript theme={null}
const res = await fetch('http://localhost:7860/api/auth/refresh', {
  method: 'POST',
  headers: { 'X-Session-Token': token }
});
const { token: newToken } = await res.json();
```

Tokens can be refreshed up to 10 minutes after they expire. Refresh preserves the tier of the original token. If the API key used to issue the token has since been disabled or changed tier, refresh will fail and you must re-authenticate via `/api/auth`.

### Token errors

| Error                               | Cause                                                                                                                        |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `Missing API key`                   | `POST /api/auth` was called without a valid API key.                                                                         |
| `Invalid or expired session token`  | Token is malformed, tampered with, or older than 30 minutes. Re-fetch from `/api/auth` and retry.                            |
| `Access denied`                     | Token was issued from a `public`-tier key and was used to access a proxied stream.                                           |
| `Session token cannot be refreshed` | Token is malformed, more than 10 minutes past expiry, or its source API key was disabled/changed. Re-fetch from `/api/auth`. |

***

## API Keys (Server-Side / Direct Access)

API keys are for server-side integrations and direct API access where the key is never exposed to end users.

Rate limits are enforced per API key — not per IP address or per user. Each request made with the same key counts toward that key's limit regardless of where it originates.

| Tier       | Rate Limit     | Streaming access |
| ---------- | -------------- | ---------------- |
| `public`   | 10 req / 60s   | No               |
| `standard` | 100 req / 60s  | Yes              |
| `partner`  | 1000 req / 60s | Yes              |

When you self-host Vyla API, a default public key is automatically created:

```
public_api_key
```

This key has a rate limit of 10 requests per minute and can only access non-streaming endpoints (`/api/health`, `/api/subtitles`, `/api/downloads`).

<Warning>
  The `public` tier cannot access the stream proxy (`/api?url=...`) or stream any video data. This restriction applies whether authenticating with a `public` API key directly **or** with a session token obtained from a `public` key. Streaming requires a `standard` or `partner` key, or a session token obtained from one.

  You must create your own `standard` or `partner` API keys by inserting them directly into your SQLite database. See [Self-Hosting](/self-hosting) for the SQL commands to add new keys.
</Warning>

### Sending your key

<CodeGroup>
  ```bash Authorization Header theme={null}
  curl "http://localhost:7860/api/health" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```bash X-API-Key Header theme={null}
  curl "http://localhost:7860/api/health" \
    -H "X-API-Key: YOUR_API_KEY"
  ```
</CodeGroup>

### API key errors

| Error                 | Cause                                                                      |
| --------------------- | -------------------------------------------------------------------------- |
| `Missing API key`     | No `Authorization`, `X-API-Key`, or `X-Session-Token` header sent.         |
| `Invalid API key`     | Key doesn't match any known key in your database.                          |
| `Rate limit exceeded` | The key's request limit has been hit for the current 60-second window.     |
| `Access denied`       | A `public`-tier key or token was used to access a proxied stream endpoint. |

***

## Rate Limiting

Rate limits are tracked per API key. Session token requests (authenticated via `X-Session-Token`) are not subject to rate limiting — only direct API key usage is counted.

When a limit is exceeded the API returns `429` with a JSON body:

```json theme={null}
{
  "error": "Rate limit exceeded",
  "resetAt": 1718000000000,
  "limit": 100,
  "window": 60000
}
```

`resetAt` is a Unix timestamp in milliseconds indicating when the window resets.

***

## Which method should I use?

Use **session tokens** if your code runs in a browser. Putting an API key in client-side JavaScript exposes it in network requests — session tokens exist to prevent that. Your server fetches the token using its API key and passes it to the client.

Use **API keys** if your code runs on a server where the key is never sent to the browser.

***

## Public vs. Protected Routes

| Route                    | Auth required       | `public` key/token allowed |
| ------------------------ | ------------------- | -------------------------- |
| `GET /`                  | No                  | Yes                        |
| `POST /api/auth`         | Yes                 | Yes                        |
| `GET /api/health`        | Yes                 | Yes                        |
| `GET /api/subtitles/*`   | Yes                 | Yes                        |
| `GET /api/downloads/*`   | Yes                 | Yes                        |
| `GET /movie`             | Yes                 | No                         |
| `GET /tv`                | Yes                 | No                         |
| `GET /api/test/:id`      | Yes                 | Yes                        |
| `GET /api/debug/:id`     | Yes                 | Yes                        |
| `POST /api/auth/refresh` | Yes (session token) | Yes                        |
| `GET /api?url=`          | Yes                 | No                         |
