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

# Quickstart

> From zero to a working HLS player in under 5 minutes.

## Before You Start

You'll need a TMDB ID for the content you want to stream. Every movie and TV show on [themoviedb.org](https://www.themoviedb.org) has one — it's the number in the URL.

| URL                          | TMDB ID | Title               |
| ---------------------------- | ------- | ------------------- |
| `themoviedb.org/movie/550`   | `550`   | Fight Club          |
| `themoviedb.org/movie/27205` | `27205` | Inception           |
| `themoviedb.org/tv/1396`     | `1396`  | Breaking Bad        |
| `themoviedb.org/tv/94997`    | `94997` | House of the Dragon |

***

## Authenticate

The Vyla API supports two authentication methods — session tokens for browser apps, and API keys for server-side integrations. See [Authentication](/authentication) for full details.

**Browser apps:** your server calls `POST /api/auth` with a valid API key to receive a session token, then passes that token to the client. The client sends `X-Session-Token` on every request. Your API key never touches the browser.

**Server-side:** send a `standard` or `partner` key as `Authorization: Bearer <key>` or `X-API-Key: <key>`.

<Note>
  The `public` key (`public_api_key`) can only access `/api/health`, `/api/subtitles`, and `/api/downloads`. Streaming endpoints (`/movie`, `/tv`, `/api/test`) require a `standard` or `partner` key, or a session token issued from one via `POST /api/auth`. `POST /api/auth` itself requires a valid API key of any tier.
</Note>

***

## Step 1 — Open the SSE Stream

The `/movie` and `/tv` endpoints are **Server-Sent Events** — not JSON. Open a streaming connection and handle three event types as they arrive: `meta`, `source`, and `done`.

<CodeGroup>
  ```bash Movie (session token) theme={null}
  TOKEN=$(curl -s -X POST https://1c34-y.hf.space/api/auth \
    -H "Authorization: Bearer YOUR_API_KEY" | jq -r .token)

  curl -N "https://1c34-y.hf.space/movie?id=550" \
    -H "X-Session-Token: $TOKEN"
  ```

  ```bash TV Episode (session token) theme={null}
  TOKEN=$(curl -s -X POST https://1c34-y.hf.space/api/auth \
    -H "Authorization: Bearer YOUR_API_KEY" | jq -r .token)

  curl -N "https://1c34-y.hf.space/tv?id=1396&season=1&episode=1" \
    -H "X-Session-Token: $TOKEN"
  ```
</CodeGroup>

<Accordion title="Example SSE stream output">
  ```
  data: {"type":"meta","meta":{"id":550,"title":"Fight Club","release_date":"1999-10-15","runtime":139},"subtitles":[{"label":"English","file":"https://sub.vdrk.site/v1/vtt/movie/550/English.vtt","type":"vtt","source":"v1"},{"label":"Spanish","file":"https://sub.vdrk.site/v1/vtt/movie/550/Spanish.vtt","type":"vtt","source":"v1"}]}

  data: {"type":"source","source":{"source":"provider-a","label":"Provider A","url":"https://1c34-y.hf.space/api?url=...&pa=1"}}

  data: {"type":"source","source":{"source":"provider-b","label":"Provider B","url":"https://1c34-y.hf.space/api?url=...&pb=1"}}

  data: {"type":"done","total":2}
  ```
</Accordion>

***

## Step 2 — Handle Events and Play

Parse each `data:` line as JSON and act on the `type` field. Start playback on the first `source` event — don't wait for `done`.

<Tabs>
  <Tab title="Vanilla JS (session token)">
    ```html theme={null}
    <!DOCTYPE html>
    <html>
    <head>
      <script src="https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js"></script>
    </head>
    <body>
      <video id="player" controls style="width:100%"></video>
      <div id="status">Connecting…</div>

      <script>
        const BASE   = 'https://1c34-y.hf.space';
        const video  = document.getElementById('player');
        const status = document.getElementById('status');

        let hls;
        let sources = [];
        let started = false;

        function playSource(url) {
          hls?.destroy();
          if (!Hls.isSupported()) { video.src = url; return; }
          hls = new Hls();
          hls.loadSource(url);
          hls.attachMedia(video);
          hls.on(Hls.Events.MANIFEST_PARSED, () => {
            status.textContent = 'Playing';
            video.play();
          });
          hls.on(Hls.Events.ERROR, (_, err) => {
            if (err.fatal) {
              const next = sources.find(s => s.url !== url);
              if (next) { status.textContent = `Trying ${next.label}…`; playSource(next.url); }
              else status.textContent = 'All sources failed.';
            }
          });
        }

        async function loadMovie(tmdbId) {
          const { token } = await fetch(`${BASE}/api/auth`, {
            method: 'POST',
            headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
          }).then(r => r.json());

          const res = await fetch(`${BASE}/movie?id=${tmdbId}`, {
            headers: { 'X-Session-Token': token }
          });
          const reader  = res.body.getReader();
          const decoder = new TextDecoder();
          let buffer    = '';

          while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            buffer += decoder.decode(value, { stream: true });
            const lines = buffer.split('\n');
            buffer = lines.pop();

            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              const event = JSON.parse(line.slice(6));

              if (event.type === 'meta') {
                status.textContent = `Loading ${event.meta?.title ?? ''}…`;
                event.subtitles?.forEach((sub, i) => {
                  const track    = document.createElement('track');
                  track.kind     = 'subtitles';
                  track.label    = sub.label;
                  track.src      = sub.file;
                  track.default  = i === 0;
                  video.appendChild(track);
                });
              }

              if (event.type === 'source') {
                sources.push(event.source);
                if (!started) { started = true; playSource(event.source.url); }
              }

              if (event.type === 'done' && !started) {
                status.textContent = 'No sources available.';
              }
            }
          }
        }

        loadMovie(550);
      </script>
    </body>
    </html>
    ```
  </Tab>

  <Tab title="React (session token)">
    ```tsx theme={null}
    import { useEffect, useRef, useState } from 'react';
    import Hls from 'hls.js';

    const BASE = 'https://1c34-y.hf.space';

    export function Player({ tmdbId }: { tmdbId: number }) {
      const videoRef   = useRef<HTMLVideoElement>(null);
      const hlsRef     = useRef<Hls | null>(null);
      const sourcesRef = useRef<string[]>([]);
      const [status, setStatus] = useState('Connecting…');

      function playUrl(url: string) {
        if (!videoRef.current) return;
        hlsRef.current?.destroy();
        if (!Hls.isSupported()) { videoRef.current.src = url; return; }

        const hls = new Hls();
        hlsRef.current = hls;
        hls.loadSource(url);
        hls.attachMedia(videoRef.current);
        hls.on(Hls.Events.MANIFEST_PARSED, () => {
          setStatus('Playing');
          videoRef.current?.play();
        });
        hls.on(Hls.Events.ERROR, (_, err) => {
          if (err.fatal) {
            const next = sourcesRef.current.find(u => u !== url);
            if (next) { setStatus('Trying next source…'); playUrl(next); }
            else setStatus('All sources failed.');
          }
        });
      }

      useEffect(() => {
        let started = false;

        (async () => {
          const { token } = await fetch(`${BASE}/api/auth`, {
            method: 'POST',
            headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
          }).then(r => r.json());

          const res = await fetch(`${BASE}/movie?id=${tmdbId}`, {
            headers: { 'X-Session-Token': token }
          });
          const reader  = res.body!.getReader();
          const decoder = new TextDecoder();
          let buffer    = '';

          while (true) {
            const { done, value } = await reader.read();
            if (done) break;

            buffer += decoder.decode(value, { stream: true });
            const lines = buffer.split('\n');
            buffer = lines.pop()!;

            for (const line of lines) {
              if (!line.startsWith('data: ')) continue;
              const event = JSON.parse(line.slice(6));

              if (event.type === 'source') {
                sourcesRef.current.push(event.source.url);
                if (!started) { started = true; playUrl(event.source.url); }
              }

              if (event.type === 'done' && !started) setStatus('No sources available.');
            }
          }
        })();

        return () => hlsRef.current?.destroy();
      }, [tmdbId]);

      return (
        <>
          <video ref={videoRef} controls style={{ width: '100%' }} />
          <p>{status}</p>
        </>
      );
    }
    ```
  </Tab>

  <Tab title="Python (server-side key)">
    ```python theme={null}
    import requests, json

    BASE = 'https://1c34-y.hf.space'

    def stream_movie(tmdb_id: int, api_key: str):
        sources   = []
        subtitles = []

        with requests.get(
            f'{BASE}/movie',
            params={'id': tmdb_id},
            headers={'Authorization': f'Bearer {api_key}'},
            stream=True
        ) as res:
            for line in res.iter_lines():
                if not line or not line.startswith(b'data: '):
                    continue
                event = json.loads(line[6:])

                if event['type'] == 'meta':
                    subtitles = event.get('subtitles', [])
                    print('Title:', event['meta'].get('title'))

                elif event['type'] == 'source':
                    sources.append(event['source'])
                    print(f"  + {event['source']['label']}: {event['source']['url']}")

                elif event['type'] == 'done':
                    print(f"Done. {event['total']} sources.")
                    break

        return sources, subtitles

    sources, subtitles = stream_movie(550, 'your_standard_api_key')
    ```
  </Tab>
</Tabs>

***

## Step 3 — Build a Fallback Queue

Sources stream in order of speed. Load the first one immediately and queue the rest — if one dies mid-stream, try the next.

```typescript theme={null}
const BASE = 'https://1c34-y.hf.space';

async function streamWithFallback(
  videoEl: HTMLVideoElement,
  tmdbId: number,
  season?: number,
  episode?: number
) {
  const { token } = await fetch(`${BASE}/api/auth`, {
    method: 'POST',
    headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
  }).then(r => r.json());

  const endpoint = season && episode
    ? `${BASE}/tv?id=${tmdbId}&season=${season}&episode=${episode}`
    : `${BASE}/movie?id=${tmdbId}`;

  const queue: string[] = [];
  let started = false;
  let hls: any;

  function tryNext() {
    if (!queue.length) return;
    hls?.destroy();
    hls = new Hls();

    const url = queue.shift()!;
    hls.loadSource(url);
    hls.attachMedia(videoEl);
    hls.on(Hls.Events.MANIFEST_PARSED, () => videoEl.play());
    hls.on(Hls.Events.ERROR, (_: any, err: any) => {
      if (err.fatal) tryNext();
    });
  }

  const res = await fetch(endpoint, {
    headers: { 'X-Session-Token': token }
  });
  const reader  = res.body!.getReader();
  const decoder = new TextDecoder();
  let buffer    = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop()!;

    for (const line of lines) {
      if (!line.startsWith('data: ')) continue;
      const event = JSON.parse(line.slice(6));

      if (event.type === 'source') {
        queue.push(event.source.url);
        if (!started) { started = true; tryNext(); }
      }

      if (event.type === 'done' && !started) {
        throw new Error('No sources available');
      }
    }
  }
}
```

***

## Step 4 — Add Subtitles

Subtitles arrive in the `meta` event, before any source. Attach them to your video element immediately.

```javascript theme={null}
if (event.type === 'meta') {
  event.subtitles.forEach((sub, i) => {
    const track   = document.createElement('track');
    track.kind    = 'subtitles';
    track.label   = sub.label;
    track.src     = sub.file;
    track.default = i === 0;
    videoEl.appendChild(track);
  });
}
```

***

## Check Provider Health

Before building, verify which providers are currently live. The health endpoint accepts any valid API key or session token.

```bash theme={null}
curl https://1c34-y.hf.space/api/health \
  -H "Authorization: Bearer public_api_key"
```

```json theme={null}
{
  "status": "ok",
  "timestamp": "2025-01-01T00:00:00.000Z",
  "tmdb": true,
  "cache": 0,
  "probe_id": "155",
  "sources": {
    "provider-a": { "ok": true,  "ms": 1204 },
    "provider-b": { "ok": true,  "ms": 980  },
    "provider-c": { "ok": false, "ms": 435  }
  }
}
```

`status: "degraded"` means at least one provider is down — the rest still work and your fallback queue handles it automatically.

***

## Debug a Single Provider

Use `/api/test` to isolate a specific provider without running the full fanout. This endpoint requires a `standard`/`partner` key or session token.

```bash theme={null}
TOKEN=$(curl -s -X POST https://1c34-y.hf.space/api/auth \
  -H "Authorization: Bearer YOUR_API_KEY" | jq -r .token)

curl "https://1c34-y.hf.space/api/test/550?source=<provider-key>" \
  -H "X-Session-Token: $TOKEN"
```

<Tip>
  Available provider keys are listed in the `/api/health` response under the `sources` field.
</Tip>

<Card title="Full API Reference →" href="/api-reference/movie">
  See complete SSE event docs for every endpoint.
</Card>
