-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin Development
Build your own compatible streaming API - Use any tech stack, any language, any infrastructure
This guide shows you how to build your own compatible streaming API that works with any frontend application. You don't need to copy our implementation - you can use any programming language, any framework, and any infrastructure you want.
A simple HTTP API with just two endpoints:
-
Manifest Endpoint (
/manifest.json) - Tells clients what your API provides -
Series Endpoint (
/api/series) - Returns video URLs for requested episodes
- Use any language: Node.js, Python, Go, Java, PHP, Ruby, Rust, etc.
- Use any framework: Express, Flask, FastAPI, Django, Spring Boot, Laravel, etc.
- Use any database: PostgreSQL, MongoDB, MySQL, or no database at all
- Use any hosting: AWS, Heroku, Vercel, DigitalOcean, your own server, etc.
- Get video URLs however you want: scraping, your own CDN, third-party APIs, database, etc.
Only these two things matter:
-
Request format: Accept
id,season,chapteras query parameters - Response format: Return JSON in the specific structure shown below
That's it! Everything else is up to you.
GET /manifest.json → Returns API metadata
GET /api/series → Returns video URL for episode
Your /api/series endpoint receives these query parameters:
GET /api/series?id=1399&season=1&chapter=1
Parameters:
-
id(number): Series ID (e.g., from TMDB, IMDB, or your own ID system) -
season(number): Season number (1, 2, 3...) -
chapter(number): Episode number (1, 2, 3...)
Your API must return JSON in this exact structure:
{
"type": "series",
"data": {
"url": "https://your-cdn.com/video.mp4",
"type": "series",
"id": "1399",
"season": 1,
"episode": 1
},
"subtitles": [
{
"url": "https://your-cdn.com/en.vtt",
"lang": "en"
}
]
}Required fields:
-
type: Always"series"(or"movie"if you support movies) -
data.url: Direct video URL (mp4, m3u8, any streamable format) -
data.type: Same astypefield -
data.id: Series ID as string -
data.season: Season number -
data.episode: Episode number
Optional fields:
-
subtitles: Array of subtitle objects (url + language)
Your /manifest.json endpoint should return:
{
"name": "My Streaming API",
"version": "1.0.0",
"description": "Your API description",
"supportedTypes": ["series"],
"endpoints": {
"series": "/api/series"
}
}Use these as reference for your language:
interface Request {
id: number;
season: number;
chapter: number;
}
interface Response {
type: "series";
data: {
url: string;
type: "series";
id: string;
season: number;
episode: number;
};
subtitles?: Array<{
url: string;
lang: string;
}>;
}class Request:
id: int
season: int
chapter: int
class Subtitle:
url: str
lang: str
class DataPayload:
url: str
type: str
id: str
season: int
episode: int
class Response:
type: str
data: DataPayload
subtitles: Optional[List[Subtitle]]type Request struct {
ID int `json:"id"`
Season int `json:"season"`
Chapter int `json:"chapter"`
}
type Subtitle struct {
URL string `json:"url"`
Lang string `json:"lang"`
}
type DataPayload struct {
URL string `json:"url"`
Type string `json:"type"`
ID string `json:"id"`
Season int `json:"season"`
Episode int `json:"episode"`
}
type Response struct {
Type string `json:"type"`
Data DataPayload `json:"data"`
Subtitles []Subtitle `json:"subtitles,omitempty"`
}public class Request {
public int id;
public int season;
public int chapter;
}
public class Subtitle {
public String url;
public String lang;
}
public class DataPayload {
public String url;
public String type;
public String id;
public int season;
public int episode;
}
public class Response {
public String type;
public DataPayload data;
public List<Subtitle> subtitles;
}// server.js
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// Manifest endpoint
app.get('/manifest.json', (req, res) => {
res.json({
name: "My Streaming API",
version: "1.0.0",
supportedTypes: ["series"],
endpoints: { series: "/api/series" }
});
});
// Series endpoint
app.get('/api/series', async (req, res) => {
const { id, season, chapter } = req.query;
if (!id || !season || !chapter) {
return res.status(400).json({ error: "Missing parameters" });
}
// YOUR LOGIC: Get video URL however you want
const videoUrl = await getVideoUrl(id, season, chapter);
if (!videoUrl) {
return res.status(404).json({ error: "Not found" });
}
res.json({
type: "series",
data: {
url: videoUrl,
type: "series",
id: String(id),
season: parseInt(season),
episode: parseInt(chapter)
}
});
});
async function getVideoUrl(id, season, chapter) {
// IMPLEMENT THIS: Return video URL from your source
// Examples:
// - Database: SELECT url FROM videos WHERE ...
// - Your CDN: return `https://cdn.example.com/${id}/s${season}e${chapter}.mp4`
// - External API: const res = await fetch(...); return res.url
// - Web scraping: const html = await fetch(...); return extractUrl(html)
return `https://example.com/video-${id}-s${season}e${chapter}.mp4`;
}
app.listen(3000);Run it:
npm install express cors
node server.jsfrom flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
@app.route('/manifest.json')
def manifest():
return jsonify({
"name": "My Streaming API",
"version": "1.0.0",
"supportedTypes": ["series"],
"endpoints": {"series": "/api/series"}
})
@app.route('/api/series')
def get_series():
series_id = request.args.get('id')
season = request.args.get('season')
chapter = request.args.get('chapter')
if not all([series_id, season, chapter]):
return jsonify({"error": "Missing parameters"}), 400
# YOUR LOGIC: Get video URL however you want
video_url = get_video_url(series_id, season, chapter)
if not video_url:
return jsonify({"error": "Not found"}), 404
return jsonify({
"type": "series",
"data": {
"url": video_url,
"type": "series",
"id": str(series_id),
"season": int(season),
"episode": int(chapter)
}
})
def get_video_url(series_id, season, chapter):
# IMPLEMENT THIS: Return video URL from your source
return f"https://example.com/video-{series_id}-s{season}e{chapter}.mp4"
if __name__ == '__main__':
app.run(port=3000)Run it:
pip install flask flask-cors
python app.pyfrom fastapi import FastAPI, Query, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional, List
app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"])
class Subtitle(BaseModel):
url: str
lang: str
class DataPayload(BaseModel):
url: str
type: str
id: str
season: int
episode: int
class SeriesResponse(BaseModel):
type: str
data: DataPayload
subtitles: Optional[List[Subtitle]] = None
@app.get("/manifest.json")
def get_manifest():
return {
"name": "My Streaming API",
"version": "1.0.0",
"supportedTypes": ["series"],
"endpoints": {"series": "/api/series"}
}
@app.get("/api/series", response_model=SeriesResponse)
async def get_series(
id: int = Query(...),
season: int = Query(...),
chapter: int = Query(...)
):
# YOUR LOGIC: Get video URL however you want
video_url = await get_video_url(id, season, chapter)
if not video_url:
raise HTTPException(status_code=404, detail="Not found")
return {
"type": "series",
"data": {
"url": video_url,
"type": "series",
"id": str(id),
"season": season,
"episode": chapter
}
}
async def get_video_url(series_id: int, season: int, chapter: int) -> str:
# IMPLEMENT THIS: Return video URL from your source
return f"https://example.com/video-{series_id}-s{season}e{chapter}.mp4"Run it:
pip install fastapi uvicorn
uvicorn main:app --port 3000package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gin-contrib/cors"
)
type Manifest struct {
Name string `json:"name"`
Version string `json:"version"`
SupportedTypes []string `json:"supportedTypes"`
Endpoints map[string]string `json:"endpoints"`
}
type DataPayload struct {
URL string `json:"url"`
Type string `json:"type"`
ID string `json:"id"`
Season int `json:"season"`
Episode int `json:"episode"`
}
type SeriesResponse struct {
Type string `json:"type"`
Data DataPayload `json:"data"`
}
func main() {
router := gin.Default()
router.Use(cors.Default())
router.GET("/manifest.json", func(c *gin.Context) {
c.JSON(http.StatusOK, Manifest{
Name: "My Streaming API",
Version: "1.0.0",
SupportedTypes: []string{"series"},
Endpoints: map[string]string{"series": "/api/series"},
})
})
router.GET("/api/series", func(c *gin.Context) {
id := c.Query("id")
season := c.Query("season")
chapter := c.Query("chapter")
if id == "" || season == "" || chapter == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing parameters"})
return
}
// YOUR LOGIC: Get video URL however you want
videoURL := getVideoURL(id, season, chapter)
if videoURL == "" {
c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
return
}
c.JSON(http.StatusOK, SeriesResponse{
Type: "series",
Data: DataPayload{
URL: videoURL,
Type: "series",
ID: id,
Season: parseInt(season),
Episode: parseInt(chapter),
},
})
})
router.Run(":3000")
}
func getVideoURL(id, season, chapter string) string {
// IMPLEMENT THIS: Return video URL from your source
return fmt.Sprintf("https://example.com/video-%s-s%se%s.mp4", id, season, chapter)
}
func parseInt(s string) int {
// Add proper parsing
return 0
}Run it:
go get github.com/gin-gonic/gin github.com/gin-contrib/cors
go run main.go<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ($path === '/manifest.json') {
echo json_encode([
'name' => 'My Streaming API',
'version' => '1.0.0',
'supportedTypes' => ['series'],
'endpoints' => ['series' => '/api/series']
]);
exit;
}
if ($path === '/api/series') {
$id = $_GET['id'] ?? null;
$season = $_GET['season'] ?? null;
$chapter = $_GET['chapter'] ?? null;
if (!$id || !$season || !$chapter) {
http_response_code(400);
echo json_encode(['error' => 'Missing parameters']);
exit;
}
// YOUR LOGIC: Get video URL however you want
$videoUrl = getVideoUrl($id, $season, $chapter);
if (!$videoUrl) {
http_response_code(404);
echo json_encode(['error' => 'Not found']);
exit;
}
echo json_encode([
'type' => 'series',
'data' => [
'url' => $videoUrl,
'type' => 'series',
'id' => (string)$id,
'season' => (int)$season,
'episode' => (int)$chapter
]
]);
exit;
}
function getVideoUrl($id, $season, $chapter) {
// IMPLEMENT THIS: Return video URL from your source
return "https://example.com/video-{$id}-s{$season}e{$chapter}.mp4";
}
?>Run it:
php -S localhost:3000# Test manifest
curl http://localhost:3000/manifest.json
# Test series endpoint
curl "http://localhost:3000/api/series?id=1399&season=1&chapter=1"Open in your browser:
http://localhost:3000/manifest.jsonhttp://localhost:3000/api/series?id=1399&season=1&chapter=1
Create two requests:
Request 1: Get Manifest
GET http://localhost:3000/manifest.json
Request 2: Get Series
GET http://localhost:3000/api/series?id=1399&season=1&chapter=1
Your /api/series response should look like this:
{
"type": "series",
"data": {
"url": "https://...",
"type": "series",
"id": "1399",
"season": 1,
"episode": 1
}
}- Check that all required fields are present
- Check that
urlis a valid string - Check that
idis a string (not number) - Check that
seasonandepisodeare numbers
-
GET /manifest.jsonendpoint -
GET /api/seriesendpoint - Accept
id,season,chapterquery parameters - Return JSON with
type,data,data.url, etc. - Enable CORS (allow cross-origin requests)
- Handle missing parameters (return 400)
- Handle not found (return 404)
- Caching (Redis, in-memory, database, etc.)
- Error logging
- Request validation
- Rate limiting
- Health check endpoint
- Subtitle support
- Multiple providers/fallbacks
- Specific database (use any or none)
- Specific caching system (use any or none)
- TypeScript (use any language)
- Specific framework (use any)
- Queue system (unless you want it)
- The exact same architecture as our implementation
async function getVideoUrl(id, season, chapter) {
const video = await db.query(
'SELECT url FROM videos WHERE series_id = ? AND season = ? AND episode = ?',
[id, season, chapter]
);
return video?.url;
}function getVideoUrl(id, season, chapter) {
// Generate CDN URL based on parameters
return `https://cdn.yoursite.com/series/${id}/s${season}e${chapter}.mp4`;
}async function getVideoUrl(id, season, chapter) {
const response = await fetch(`https://api.videosource.com/get?id=${id}&s=${season}&e=${chapter}`);
const data = await response.json();
return data.stream_url;
}async function getVideoUrl(id, season, chapter) {
const html = await fetch(`https://streaming-site.com/series/${id}/s${season}e${chapter}`);
const text = await html.text();
// Extract video URL from HTML
const match = text.match(/videoUrl: "([^"]+)"/);
return match ? match[1] : null;
}async function getVideoUrl(id, season, chapter) {
// Try source 1
try {
const url1 = await getFromSource1(id, season, chapter);
if (url1) return url1;
} catch (e) {}
// Try source 2
try {
const url2 = await getFromSource2(id, season, chapter);
if (url2) return url2;
} catch (e) {}
// All sources failed
return null;
}Your API is just a simple HTTP server. Deploy it anywhere you want:
-
Heroku:
git push heroku main -
Vercel:
vercel deploy - AWS Lambda: Use serverless framework
- DigitalOcean: Deploy on droplet
- Your own server: Run with PM2 or systemd
- Docker: Build and run container
- Railway: Connect GitHub repo
-
Fly.io:
fly deploy
Set these on your deployment platform:
PORT=3000 # Optional: Server port
VIDEO_API_KEY=xyz # Optional: If using external API
DATABASE_URL=postgres://... # Optional: If using database
REDIS_URL=redis://... # Optional: If using cachingNo. Use any language you're comfortable with.
No. Plain JavaScript, Python, Go, PHP, anything works.
No. Use any caching method or no caching at all.
No. You can generate URLs dynamically, scrape websites, call other APIs, etc.
No. The reference implementation uses queues, providers, TypeScript, etc., but none of that is required. Just implement the two endpoints with the correct response format.
Any streamable format: .mp4, .m3u8, .mpd, etc. The frontend (video player) handles the format, not your API.
Yes! Add any authentication you want. Just make sure your endpoints return the correct JSON format.
Yes! The two shown endpoints are the minimum. Add as many as you want.
That's completely up to you:
- Host your own videos
- Scrape from websites
- Use a third-party API
- Store URLs in a database
- Generate URLs dynamically
-
Two endpoints:
GET /manifest.jsonGET /api/series?id=X&season=Y&chapter=Z
-
Correct response format:
{ "type": "series", "data": { "url": "video-url-here", "type": "series", "id": "123", "season": 1, "episode": 1 } } -
CORS enabled (allow cross-origin requests)
- Database, caching, queue system, specific framework, TypeScript, etc.
- Pick a language/framework you know
- Create two endpoints
- Return hardcoded JSON to test
- Implement your video URL logic
- Deploy it
- Test with the frontend
That's it! You now have a compatible streaming API.
Questions? Open an issue or contact us.
Good luck building your API!