HTTP Server
startServer launches a Node http.Server that exposes the SDK’s operations as HTTP routes: content-provider routes (/search, /content, /stream, /tracks), metadata routes (/meta/search, /meta/info, /meta/content, /meta/stream, /meta/tracks, /meta/browse), discovery (/health, /openapi.json), and an optional /proxy. Pass the providers and metadata 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, AnilistMeta, MalMeta, startServer,} from 'anime-sdk';
const http = new HttpClient();
const server = startServer({ providers: [new AllmangaProvider(http), new GogoanimeProvider(http)], metaProviders: [new AnilistMeta(http), new MalMeta(http)], port: 3000, proxy: true, proxySignSecret: process.env.PROXY_SECRET, // HMAC-sign /proxy URLs (recommended in prod) proxyAllowedHosts: ['wixstatic.com', 'allanime.day'], // SSRF allowlist (optional)});
// 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.
Metadata routes
Section titled “Metadata routes”The metadata layer is exposed under /meta/*. Each route takes
provider=<metaProviderId> (e.g. anilist, mal, kitsu) and the
mapping to a content provider happens server-side.
GET /meta/search
Section titled “GET /meta/search”curl 'localhost:3000/meta/search?provider=anilist&q=Cowboy%20Bebop'GET /meta/info
Section titled “GET /meta/info”curl 'localhost:3000/meta/info?provider=anilist&id=anilist:1'Returns the full IMediaMetadata record: title, description, cover,
relations (sequel/prequel), characters (with voice actors), staff,
recommendations, external links, streamingEpisodes, and mappings
(cross-source IDs).
The server validates that the URN’s prefix matches the meta provider —
hitting /meta/info?provider=anilist&id=mal:21 returns 400.
GET /meta/content
Section titled “GET /meta/content”List episodes/chapters for a meta URN on a specific content provider. Mapping is automatic.
curl 'localhost:3000/meta/content?provider=anilist&id=anilist:1&contentProvider=allmanga'GET /meta/stream / GET /meta/tracks
Section titled “GET /meta/stream / GET /meta/tracks”Resolve a stream / tracks by metadata + episode number:
curl 'localhost:3000/meta/stream?provider=anilist&id=anilist:1&episode=1&contentProvider=allmanga&language=sub'GET /meta/browse
Section titled “GET /meta/browse”Trending / popular / seasonal / top from the catalogue:
curl 'localhost:3000/meta/browse?provider=anilist&kind=trending&perPage=10'curl 'localhost:3000/meta/browse?provider=anilist&kind=seasonal&season=FALL&year=2024'Discovery
Section titled “Discovery”GET /health returns a JSON summary of the server’s capabilities
(registered providers, registered meta providers, whether /proxy is
enabled).
GET /openapi.json returns an OpenAPI 3.1 spec describing every route —
feed it to Swagger UI / Redoc / a codegen tool.
Signing /proxy URLs
Section titled “Signing /proxy URLs”Setting proxySignSecret requires every /proxy?url=... to carry a
matching sig HMAC-SHA256 query parameter. The signature is computed over
url plus optional |h=<h>. The proxy rewriter automatically signs URLs
it emits, so callers using /stream / /meta/stream don’t need to know
about it.
import * as crypto from 'node:crypto';const sig = crypto.createHmac('sha256', secret).update(targetUrl).digest('hex');Unsigned or invalid-signature requests return 401.
SSRF allowlist
Section titled “SSRF allowlist”proxyAllowedHosts: string[] restricts /proxy to a suffix-matched list
of upstream hosts. Each entry covers all subdomains:
proxyAllowedHosts: ['wixstatic.com', 'allanime.day', 'mp4upload.com'];Targets outside the list return 403. When unset, all hosts are allowed — fine for local development, risky in production (the server otherwise becomes an open relay).
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.