HTTP Server
startServer launches a Node http.Server that exposes the SDK’s operations as HTTP routes: /search, /content, /stream, /tracks, and (optionally) /proxy. Pass the providers you want to expose, plus optional flags for the proxy, a bearer token, and a cache.
Starting the server
Section titled “Starting the server”import { HttpClient, AllmangaProvider, GogoanimeProvider, startServer } from 'anime-sdk';
const server = startServer({ providers: [ new AllmangaProvider(new HttpClient()), new GogoanimeProvider(new HttpClient({ timeoutMs: 10_000 })), ], port: 3000, // default: 3000 proxy: true, // optional: enables /proxy and rewrites stream + subtitle URLs through it});
// server is an http.Server: you can call server.close() to shut it downThe server logs anime-sdk server listening on http://localhost:3000 when ready.
Routes
Section titled “Routes”All routes are GET only. Errors return { error: string } with the appropriate HTTP status.
GET /search
Section titled “GET /search”Search for shows or manga.
| Param | Required | Description |
|---|---|---|
q | ✓ | Search query string |
provider | ✓ | Provider ID: allmanga, gogoanime, anikoto, mangadex, weebcentral, or mangapill … |
curl 'localhost:3000/search?q=Frieren&provider=allmanga'Response: IMediaSearchResult[]
[ { "id": "frieren-beyond-journeys-end", "title": "Frieren: Beyond Journey's End", "thumbnailUrl": "https://...", "catalogType": "ANIME", "providerId": "allmanga", "availableLanguages": ["sub", "dub"] }]GET /content
Section titled “GET /content”List episodes for a show or chapters for a manga. One call returns the unified, language-agnostic list: each unit advertises its translations.
| Param | Required | Description |
|---|---|---|
mediaId | ✓ | id from a search result |
provider | ✓ | Provider ID |
curl 'localhost:3000/content?mediaId=frieren-beyond-journeys-end&provider=allmanga'Response: IContentUnit[]
[ { "id": "frieren-beyond-journeys-end/1", "title": "Episode 1", "number": 1, "availableLanguages": ["sub", "dub"] }]GET /stream
Section titled “GET /stream”Resolve a direct stream URL for an episode or image URLs for a manga chapter. Pick the translation here (if applicable).
| Param | Required | Description |
|---|---|---|
unitId | ✓ | id from a content result |
provider | ✓ | Provider ID |
language | sub | dub | raw |
curl 'localhost:3000/stream?unitId=frieren-beyond-journeys-end%2F1&provider=allmanga&language=dub'Response: ResolvedMediaStream
Anime response:
{ "type": "video", "streams": [ { "sourceUrl": "https://a4.mp4upload.com:183/d/.../video.mp4", "isHLS": false, "quality": "auto", "language": "dub", "headers": { "Referer": "https://mp4upload.com/" }, "subtitles": [ { "url": "https://cdn.example.com/subs/en.vtt", "language": "en", "label": "English", "format": "vtt" } ] } ]}Manga response:
{ "type": "manga", "pages": { "imageUrls": ["https://...", "https://..."], "headers": { "Referer": "https://..." } }}When proxy: true, both sourceUrl, anime subtitle urls, and manga imageUrls are rewritten through /proxy automatically.
GET /tracks
Section titled “GET /tracks”Inspect subtitle + quality availability for a unit without resolving the playable stream. Useful for populating a subtitle selector before the user hits play.
| Param | Required | Description |
|---|---|---|
unitId | ✓ | id from a content result |
provider | ✓ | Provider ID |
language | sub | dub | raw |
curl 'localhost:3000/tracks?unitId=...&provider=animeparadise&language=sub'Response: IUnitTracks
{ "subtitles": [ { "url": "http://localhost:3000/proxy?url=...&ct=text%2Fvtt", "label": "English", "language": "en", "format": "vtt" } ], "qualities": ["auto"]}Returns 501 if the provider doesn’t implement fetchUnitTracks: read subtitles from /stream’s response instead.
GET /download/video/progress (SSE)
Section titled “GET /download/video/progress (SSE)”Resolve and download an episode to the server’s disk, streaming live progress events via Server-Sent Events.
| Param | Required | Description |
|---|---|---|
unitId | ✓ | id from a content result |
provider | ✓ | Provider ID |
language | sub | dub | raw |
curl -N 'localhost:3000/download/video/progress?unitId=...&provider=allmanga&language=sub'Events arrive as data: <JSON>\n\n. Three event shapes:
// While downloading:{ "type": "progress", "phase": "downloading", "detail": "Segment 12/45" }{ "type": "progress", "phase": "muxing", "detail": "Muxing segments to MP4 via ffmpeg" }
// On success: retrieve the file with /download/video/file?token=<token>:{ "type": "complete", "token": "550e8400-e29b-41d4-a716-446655440000" }
// On failure:{ "type": "error", "message": "downloadVideo exhausted all candidates…" }Progress phase values in order: resolving → downloading → muxing → complete.
GET /download/video/file
Section titled “GET /download/video/file”Retrieve a completed video download by token. One-time: the file is deleted after the first successful response.
| Param | Required | Description |
|---|---|---|
token | ✓ | Token from a /download/video/progress complete event |
Response: video/mp4 with Content-Disposition: attachment. Tokens expire after 10 minutes.
curl -OJ 'localhost:3000/download/video/file?token=550e8400-…'GET /download/manga/chapter/progress (SSE)
Section titled “GET /download/manga/chapter/progress (SSE)”Same as /download/video/progress but for manga chapters. Downloads all pages and packages them as a ZIP archive.
| Param | Required | Description |
|---|---|---|
unitId | ✓ | id from a content result |
provider | ✓ | Provider ID |
{ "type": "progress", "downloaded": 8, "total": 24 }{ "type": "complete", "token": "…" }{ "type": "error", "message": "…" }GET /download/manga/chapter/file
Section titled “GET /download/manga/chapter/file”Retrieve a completed chapter ZIP by token (one-time, 10-minute expiry).
Response: application/zip with Content-Disposition: attachment.
GET /download/video (direct)
Section titled “GET /download/video (direct)”Download a video without progress streaming. Resolves the stream, downloads to a server temp file via downloadVideo, then serves the completed MP4 in one response. Suitable for programmatic use when SSE is inconvenient.
| Param | Required | Description |
|---|---|---|
unitId | ✓ | Unit ID |
provider | ✓ | Provider ID |
language | sub | dub | raw |
Response: video/mp4 with Content-Disposition: attachment. Timeout: 20 minutes.
GET /download/manga/page
Section titled “GET /download/manga/page”Download a single manga page image.
| Param | Required | Description |
|---|---|---|
unitId | ✓ | Unit ID |
provider | ✓ | Provider ID |
page | 0-based page index (default: 0) |
Response: image file (image/jpeg, image/png, etc.) with Content-Disposition: attachment.
GET /download/manga/chapter (direct)
Section titled “GET /download/manga/chapter (direct)”Download an entire chapter as a ZIP without progress streaming.
| Param | Required | Description |
|---|---|---|
unitId | ✓ | Unit ID |
provider | ✓ | Provider ID |
Response: application/zip with Content-Disposition: attachment.
Authentication
Section titled “Authentication”Lock the server behind a bearer token:
startServer({ providers: [new AllmangaProvider(new HttpClient())], port: 3000, auth: { token: process.env.API_TOKEN! },});Clients include the token in the Authorization header:
curl -H 'Authorization: Bearer mysecret' 'localhost:3000/search?q=Naruto&provider=allmanga'Requests without a valid token receive 401 Unauthorized. Skip auth entirely for local use.
Caching
Section titled “Caching”startServer accepts an optional cache that memoises provider calls across /search, /content, /stream, and /tracks. The contract is two methods:
interface SdkCache { get(key: string): unknown | Promise<unknown>; set(key: string, value: unknown): void | Promise<void>;}A plain Map satisfies it. Bring whatever store you like (Redis, SQLite, edge KV):
const store = new Map();const cache = { get: (key) => store.get(key), set: (key, value) => void store.set(key, value),};
startServer({ providers: [new AllmangaProvider(new HttpClient())], port: 3000, proxy: true, cache,});Keys are namespaced so different endpoints can have different policies:
| Prefix | Key shape | Notes |
|---|---|---|
search: | search:<providerId>:<query> | Stable; safe to cache for long. |
content: | content:<providerId>:<mediaId> | Stable; safe to cache for long. |
stream: | stream:<providerId>:<unitId>:<lang> | Stream URLs may carry signed expiries: apply a short TTL or skip. |
tracks: | tracks:<providerId>:<unitId>:<lang> | Cheap to compute when the provider supports it; cache freely. |
get returns undefined for a miss; any other value (including null) is treated as a hit and served as-is. To skip caching a specific endpoint, inspect the prefix in your set and bail out.
Error responses
Section titled “Error responses”| Status | Meaning |
|---|---|
| 400 | Missing or unknown query parameter |
| 401 | Missing or invalid bearer token |
| 404 | Unknown route |
| 405 | Non-GET request |
| 500 | Provider threw an error (message is included in error field) |
Calling from other languages
Section titled “Calling from other languages”The server is the bridge for any client that can’t run Node directly.
Python:
import httpx
base = 'http://localhost:3000'headers = {'Authorization': 'Bearer mysecret'}
shows = httpx.get(f'{base}/search', params={'q': 'Frieren', 'provider': 'allmanga'}, headers=headers).json()eps = httpx.get(f'{base}/content', params={'mediaId': shows[0]['id'], 'provider': 'allmanga'}, headers=headers).json()data = httpx.get(f'{base}/stream', params={'unitId': eps[0]['id'], 'provider': 'allmanga'}, headers=headers).json()url = data['streams'][0]['sourceUrl']Swift (iOS):
let url = URL(string: "http://localhost:3000/search?q=Frieren&provider=allmanga")!let (data, _) = try await URLSession.shared.data(from: url)let shows = try JSONDecoder().decode([MediaSearchResult].self, from: data)Kotlin / Android:
val client = OkHttpClient()val request = Request.Builder() .url("http://10.0.2.2:3000/search?q=Frieren&provider=allmanga") .build()val response = client.newCall(request).execute()Note: in Android emulators, localhost on the host machine is reachable at 10.0.2.2.