TV Episode Sources
curl --request GET \
--url http://localhost:7860/tv \
--header 'Authorization: Bearer <token>'import requests
url = "http://localhost:7860/tv"
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/tv', 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/tv",
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/tv"
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/tv")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:7860/tv")
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_bodydata: {"type":"meta","meta":{"id":1396,"name":"Pilot","season_number":1,"episode_number":1,"air_date":"2008-01-20"},"subtitles":[{"label":"English","file":"https://sub.vdrk.site/v1/tv/1396/1/1/English.vtt","type":"vtt","source":"v1"}]}
data: {"type":"source","source":{"source":"vidlink","label":"VidLink","url":"https://api.vyla.cc/api?url=https%3A%2F%2F...&vl=1"}}
data: {"type":"source","source":{"source":"meowtv","label":"MeowTV","url":"https://api.vyla.cc/api?url=https%3A%2F%2F...&mt=1"}}
data: {"type":"done","total":2}
{ "error": "missing parameters", "route": "/tv?id=:id&season=:s&episode=:e", "example": "/tv?id=1396&season=1&episode=1" }
{ "error": "Public keys cannot access streaming endpoints" }
Stream Sources
TV Episode Sources
Stream verified HLS sources, subtitle tracks, and TMDB metadata for a TV episode via Server-Sent Events.
GET
/
tv
TV Episode Sources
curl --request GET \
--url http://localhost:7860/tv \
--header 'Authorization: Bearer <token>'import requests
url = "http://localhost:7860/tv"
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/tv', 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/tv",
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/tv"
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/tv")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:7860/tv")
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_bodydata: {"type":"meta","meta":{"id":1396,"name":"Pilot","season_number":1,"episode_number":1,"air_date":"2008-01-20"},"subtitles":[{"label":"English","file":"https://sub.vdrk.site/v1/tv/1396/1/1/English.vtt","type":"vtt","source":"v1"}]}
data: {"type":"source","source":{"source":"vidlink","label":"VidLink","url":"https://api.vyla.cc/api?url=https%3A%2F%2F...&vl=1"}}
data: {"type":"source","source":{"source":"meowtv","label":"MeowTV","url":"https://api.vyla.cc/api?url=https%3A%2F%2F...&mt=1"}}
data: {"type":"done","total":2}
{ "error": "missing parameters", "route": "/tv?id=:id&season=:s&episode=:e", "example": "/tv?id=1396&season=1&episode=1" }
{ "error": "Public keys cannot access streaming endpoints" }
Identical to the movie endpoint in behavior, but requires a series ID plus season and episode numbers. Results stream via Server-Sent Events — the
Emitted first, before any provider resolves.
Emitted once per verified, working provider.
meta event fires immediately with subtitles and TMDB data, then each working provider emits its own source event as it resolves.
This endpoint requires a
standard or partner API key, or a session token from POST /api/auth. The public key will receive a 403 response.Query Parameters
string
required
TMDB series ID — not an episode ID. Find it on themoviedb.org in the URL of the show’s main page.Example:
themoviedb.org/tv/1396 → id=1396 (Breaking Bad)number
required
Season number. Use
1 for the first season.number
required
Episode number within the season. Use
1 for the first episode.string
Comma-separated list of provider keys to query. When omitted, all active providers are queried.Example:
sources=vidlink,vixsrcUse GET /api?sources_meta=1 to retrieve the full list of available provider keys.Request
curl -N "http://localhost:7860/tv?id=1396&season=1&episode=1" \
--header "Authorization: Bearer YOUR_API_KEY"
TOKEN=$(curl -s -X POST http://localhost:7860/api/auth | jq -r .token)
curl -N "http://localhost:7860/tv?id=1396&season=1&episode=1" \
-H "X-Session-Token: $TOKEN"
const BASE = 'http://localhost:7860';
const { token } = await fetch(`${BASE}/api/auth`, { method: 'POST' }).then(r => r.json());
const res = await fetch(`${BASE}/tv?id=1396&season=1&episode=1`, {
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') handleMeta(event);
if (event.type === 'source') handleSource(event.source);
if (event.type === 'done') console.log(`Done. ${event.total} sources.`);
}
}
const BASE = 'http://localhost:7860';
interface Source {
source: string;
label: string;
url: string;
}
interface Subtitle {
label: string;
file: string;
type: string;
source: string;
}
interface MetaEvent { type: 'meta'; meta: Record<string, unknown> | null; subtitles: Subtitle[]; }
interface SourceEvent { type: 'source'; source: Source; }
interface DoneEvent { type: 'done'; total: number; }
type SSEEvent = MetaEvent | SourceEvent | DoneEvent;
async function streamEpisode(
seriesId: number,
season: number,
episode: number,
onSource: (s: Source) => void,
onMeta?: (meta: MetaEvent) => void
) {
const { token } = await fetch(`${BASE}/api/auth`, { method: 'POST' }).then(r => r.json());
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 = '';
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: SSEEvent = JSON.parse(line.slice(6));
if (event.type === 'meta') onMeta?.(event);
if (event.type === 'source') onSource(event.source);
}
}
}
import requests, json
BASE = 'http://localhost:7860'
with requests.get(
f'{BASE}/tv',
params={'id': 1396, 'season': 1, 'episode': 1},
headers={'Authorization': 'Bearer your_standard_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':
print('Episode:', event['meta'].get('name'))
elif event['type'] == 'source':
s = event['source']
print(f"{s['label']}: {s['url']}")
elif event['type'] == 'done':
print(f"Done. {event['total']} sources.")
break
let url = URL(string: "http://localhost:7860/tv?id=1396&season=1&episode=1")!
var request = URLRequest(url: url)
request.setValue("your_session_token", forHTTPHeaderField: "X-Session-Token")
class SSEDelegate: NSObject, URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard let text = String(data: data, encoding: .utf8) else { return }
for line in text.components(separatedBy: "\n") {
guard line.hasPrefix("data: "),
let json = line.dropFirst(6).data(using: .utf8),
let event = try? JSONSerialization.jsonObject(with: json) as? [String: Any]
else { continue }
print(event)
}
}
}
let delegate = SSEDelegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
session.dataTask(with: request).resume()
SSE Event Reference
meta
Emitted first, before any provider resolves.
string
required
Always
"meta".object | null
TMDB episode-level metadata — name, overview, air date, episode number, season number.
null if no TMDB_API_KEY is configured.Subtitle[]
required
source
Emitted once per verified, working provider.
string
required
Always
"source".Source
required
Show Source object
Show Source object
done
string
required
Always
"done".number
required
Total number of working
source events emitted.Status Codes
| Status | Meaning |
|---|---|
200 | SSE stream opened successfully |
400 | Missing id, season, or episode parameter |
401 | Missing or invalid authentication |
403 | Key tier does not have streaming access (public key) |
500 | Server error before the stream could begin |
data: {"type":"meta","meta":{"id":1396,"name":"Pilot","season_number":1,"episode_number":1,"air_date":"2008-01-20"},"subtitles":[{"label":"English","file":"https://sub.vdrk.site/v1/tv/1396/1/1/English.vtt","type":"vtt","source":"v1"}]}
data: {"type":"source","source":{"source":"vidlink","label":"VidLink","url":"https://api.vyla.cc/api?url=https%3A%2F%2F...&vl=1"}}
data: {"type":"source","source":{"source":"meowtv","label":"MeowTV","url":"https://api.vyla.cc/api?url=https%3A%2F%2F...&mt=1"}}
data: {"type":"done","total":2}
{ "error": "missing parameters", "route": "/tv?id=:id&season=:s&episode=:e", "example": "/tv?id=1396&season=1&episode=1" }
{ "error": "Public keys cannot access streaming endpoints" }
Notes
Filtering providers with sources=
Filtering providers with sources=
Pass a comma-separated list of provider keys to query only specific providers:Keys that don’t match any active provider are silently ignored. If none of the requested keys match, the response will emit
/tv?id=1396&season=1&episode=1&sources=vidlink,vixsrc
done with total: 0. Omit the parameter entirely to query all active providers.Series ID vs Episode ID
Series ID vs Episode ID
The
id parameter is the series TMDB ID — the same value regardless of which season or episode you request. Season and episode numbers are passed separately.| Wrong | Right |
|---|---|
| Episode-level TMDB ID | Series-level TMDB ID |
id=62085 (Pilot episode) | id=1396 (Breaking Bad series) |
Specials and bonus content
Specials and bonus content
Season
0 typically contains specials on TMDB. Support varies by provider — expect fewer working sources for specials.Meta field is episode-level
Meta field is episode-level
Unlike the movie endpoint where
meta describes the film, here meta contains episode data from TMDB — title, overview, air date, and episode number. Series-level metadata is not included.Zero sources
Zero sources
If all providers fail, you’ll receive a
done event with total: 0 and no source events. Check /api/health to see which providers are up.Using EventSource vs fetch streaming
Using EventSource vs fetch streaming
EventSource does not support custom headers, so you cannot send X-Session-Token through it. Use the fetch + ReadableStream approach shown in the examples above when authenticating with a session token.
