Skip to main content
GET
/
api
/
subtitles
/
tv
/
{tmdb_id}
/
{season}
/
{episode}
TV Subtitles
curl --request GET \
  --url https://missourimonster-vyla.hf.space/api/subtitles/tv/{tmdb_id}/{season}/{episode}
{
  "[]": [
    {
      "label": "<string>",
      "file": "<string>",
      "type": "<string>",
      "source": "<string>"
    }
  ]
}
Returns an array of subtitle tracks for a specific TV episode. Each track is a direct .vtt or .srt link — no proxy needed. Subtitle tracks are also bundled automatically in /api/tv responses; use this endpoint when you need them independently.

Path Parameters

tmdb_id
string
required
TMDB series ID — not episode ID.Example: 1396 for Breaking Bad
season
number
required
Season number.
episode
number
required
Episode number within the season.

Request

curl https://missourimonster-vyla.hf.space/api/subtitles/tv/1396/1/1

Response

Returns a JSON array. 404 if no subtitles are found for this episode.
[]
Subtitle[]
Array of subtitle track objects for the episode.

Responses

[
  {
    "label": "English",
    "file": "https://sub.vdrk.site/v1/tv/1396/1/1/English.vtt",
    "type": "vtt",
    "source": "v1"
  },
  {
    "label": "Spanish",
    "file": "https://sub.vdrk.site/v1/tv/1396/1/1/Spanish.vtt",
    "type": "vtt",
    "source": "v1"
  }
]

Building an Episode Selector with Subtitles

const BASE = 'https://missourimonster-vyla.hf.space';

async function loadEpisode(
  seriesId: number,
  season: number,
  episode: number,
  videoEl: HTMLVideoElement
) {
  // Remove existing subtitle tracks
  Array.from(videoEl.querySelectorAll('track')).forEach(t => t.remove());

  // /tv is an SSE stream — must be consumed line by line
  const res     = await fetch(`${BASE}/tv?id=${seriesId}&season=${season}&episode=${episode}`);
  const reader  = res.body!.getReader();
  const decoder = new TextDecoder();
  let buffer    = '';
  let started   = false;
  let hls: any;

  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') {
        // Attach subtitle tracks as soon as meta arrives
        (event.subtitles ?? []).forEach((sub: { label: string; file: string }, i: number) => {
          const track   = document.createElement('track');
          track.kind    = 'subtitles';
          track.label   = sub.label;
          track.src     = sub.file;
          track.default = i === 0;
          videoEl.appendChild(track);
        });
      }

      if (event.type === 'source' && !started) {
        started = true;
        hls?.destroy();
        // Import Hls dynamically if needed; assume it's already in scope here
        hls = new Hls();
        hls.loadSource(event.source.url);
        hls.attachMedia(videoEl);
        hls.on(Hls.Events.MANIFEST_PARSED, () => videoEl.play());
      }

      if (event.type === 'done' && !started) {
        throw new Error('No sources available for this episode');
      }
    }
  }
}
Subtitle availability varies by episode. Newer or less popular shows may return an empty array. Always handle subtitles.length === 0 gracefully in your UI.