Skip to content

Plugin Development

Muhammed Kaplan edited this page Oct 4, 2025 · 1 revision

API Implementation Guide

Build your own compatible streaming API - Use any tech stack, any language, any infrastructure


Overview

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.

What You're Building

A simple HTTP API with just two endpoints:

  1. Manifest Endpoint (/manifest.json) - Tells clients what your API provides
  2. Series Endpoint (/api/series) - Returns video URLs for requested episodes

Complete Flexibility

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

What You Must Follow

Only these two things matter:

  1. Request format: Accept id, season, chapter as query parameters
  2. Response format: Return JSON in the specific structure shown below

That's it! Everything else is up to you.


API Contract

Required Endpoints

GET /manifest.json     → Returns API metadata
GET /api/series        → Returns video URL for episode

Request Format

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

Response Format

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 as type field
  • data.id: Series ID as string
  • data.season: Season number
  • data.episode: Episode number

Optional fields:

  • subtitles: Array of subtitle objects (url + language)

Manifest Format

Your /manifest.json endpoint should return:

{
  "name": "My Streaming API",
  "version": "1.0.0",
  "description": "Your API description",
  "supportedTypes": ["series"],
  "endpoints": {
    "series": "/api/series"
  }
}

Type Definitions

Use these as reference for your language:

TypeScript

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;
  }>;
}

Python

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]]

Go

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"`
}

Java

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;
}

Implementation Examples

Example 1: Node.js + Express (Minimal)

// 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.js

Example 2: Python + Flask

from 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.py

Example 3: Python + FastAPI

from 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 3000

Example 4: Go + Gin

package 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

Example 5: PHP (No Framework)

<?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

Testing Your API

1. Test with cURL

# Test manifest
curl http://localhost:3000/manifest.json

# Test series endpoint
curl "http://localhost:3000/api/series?id=1399&season=1&chapter=1"

2. Test with Browser

Open in your browser:

  • http://localhost:3000/manifest.json
  • http://localhost:3000/api/series?id=1399&season=1&chapter=1

3. Test with Postman

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

4. Verify Response Format

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 url is a valid string
  • Check that id is a string (not number)
  • Check that season and episode are numbers

Implementation Checklist

Must Have

  • GET /manifest.json endpoint
  • GET /api/series endpoint
  • Accept id, season, chapter query 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)

Optional But Recommended

  • Caching (Redis, in-memory, database, etc.)
  • Error logging
  • Request validation
  • Rate limiting
  • Health check endpoint
  • Subtitle support
  • Multiple providers/fallbacks

Don't Need

  • 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

Common Implementation Patterns

Pattern 1: Database Lookup

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;
}

Pattern 2: CDN URLs

function getVideoUrl(id, season, chapter) {
  // Generate CDN URL based on parameters
  return `https://cdn.yoursite.com/series/${id}/s${season}e${chapter}.mp4`;
}

Pattern 3: External API

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;
}

Pattern 4: Web Scraping

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;
}

Pattern 5: Multiple Sources with Fallback

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;
}

Deployment

Deploy Anywhere

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

Environment Variables

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 caching

FAQ

Q: Do I need to use Node.js?

No. Use any language you're comfortable with.

Q: Do I need TypeScript?

No. Plain JavaScript, Python, Go, PHP, anything works.

Q: Do I need Redis for caching?

No. Use any caching method or no caching at all.

Q: Do I need a database?

No. You can generate URLs dynamically, scrape websites, call other APIs, etc.

Q: Do I need to use the same architecture as the reference implementation?

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.

Q: What video formats are supported?

Any streamable format: .mp4, .m3u8, .mpd, etc. The frontend (video player) handles the format, not your API.

Q: Can I add authentication?

Yes! Add any authentication you want. Just make sure your endpoints return the correct JSON format.

Q: Can I add more endpoints?

Yes! The two shown endpoints are the minimum. Add as many as you want.

Q: How do I get the video URLs?

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

Summary

You Only Need:

  1. Two endpoints:

    • GET /manifest.json
    • GET /api/series?id=X&season=Y&chapter=Z
  2. Correct response format:

    {
      "type": "series",
      "data": {
        "url": "video-url-here",
        "type": "series",
        "id": "123",
        "season": 1,
        "episode": 1
      }
    }
  3. CORS enabled (allow cross-origin requests)

Everything Else is Optional:

  • Database, caching, queue system, specific framework, TypeScript, etc.

Start Simple:

  1. Pick a language/framework you know
  2. Create two endpoints
  3. Return hardcoded JSON to test
  4. Implement your video URL logic
  5. Deploy it
  6. 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!