TV Subtitles
curl --request GET \
--url http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode} \
--header 'Authorization: Bearer <token>'import requests
url = "http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "7860",
CURLOPT_URL => "http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"[]": [
{
"label": "<string>",
"file": "<string>",
"type": "<string>",
"source": "<string>"
}
]
}Subtitles
TV Subtitles
Fetch all available subtitle tracks for a TV episode.
GET
/
api
/
subtitles
/
tv
/
{tmdb_id}
/
{season}
/
{episode}
TV Subtitles
curl --request GET \
--url http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode} \
--header 'Authorization: Bearer <token>'import requests
url = "http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "7860",
CURLOPT_URL => "http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:7860/api/subtitles/tv/{tmdb_id}/{season}/{episode}")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"[]": [
{
"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. Requires a standard or partner API key. Subtitle tracks are also bundled automatically in /tv responses as part of the meta event; use this endpoint when you need them independently.
Path Parameters
string
required
TMDB series ID — not episode ID.Example:
1396 for Breaking Badnumber
required
Season number.
number
required
Episode number within the season.
Request
curl http://localhost:7860/api/subtitles/tv/1396/1/1 \
--header "Authorization: Bearer YOUR_API_KEY"
const subtitles = await fetch(
'http://localhost:7860/api/subtitles/tv/1396/1/1',
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
).then(r => r.json());
subtitles.forEach(sub => {
console.log(`${sub.label} (${sub.type}): ${sub.file}`);
});
interface Subtitle {
label: string;
file: string;
type: string;
source: string;
}
const subtitles: Subtitle[] = await fetch(
`http://localhost:7860/api/subtitles/tv/${seriesId}/${season}/${episode}`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
).then(r => r.json());
import requests
series_id = 1396
season = 1
episode = 1
subtitles = requests.get(
f'http://localhost:7860/api/subtitles/tv/{series_id}/{season}/{episode}',
headers={'Authorization': 'Bearer YOUR_API_KEY'}
).json()
for sub in subtitles:
print(f"{sub['label']} ({sub['type']}): {sub['file']}")
Response
Returns a JSON array.404 if no subtitles are found for this episode.
Subtitle[]
Responses
- 200 — Success
- 404 — Not Found
- 401 — Unauthorized
- 403 — Forbidden
- 500 — Server Error
[
{
"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"
}
]
{ "error": "no subtitles found" }
{ "error": "Missing API key. Provide via Authorization header or X-API-Key header." }
{ "error": "Public keys cannot access subtitle endpoints" }
{ "error": "<error message>" }
Building an Episode Selector with Subtitles
const BASE = 'http://localhost:7860';
async function loadEpisode(
seriesId: number,
season: number,
episode: number,
videoEl: HTMLVideoElement,
token: string
) {
Array.from(videoEl.querySelectorAll('track')).forEach(t => t.remove());
const res = await fetch(`${BASE}/tv?id=${seriesId}&season=${season}&episode=${episode}`, {
headers: { 'X-Session-Token': token }
});
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') {
(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();
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.
