Skip to content

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.

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 down

The server logs anime-sdk server listening on http://localhost:3000 when ready.

All routes are GET only. Errors return { error: string } with the appropriate HTTP status.

Search for shows or manga.

ParamRequiredDescription
qSearch query string
providerProvider ID: allmanga, gogoanime, anikoto, mangadex, weebcentral, or mangapill
Terminal window
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"]
}
]

List episodes for a show or chapters for a manga. One call returns the unified, language-agnostic list — each unit advertises its translations.

ParamRequiredDescription
mediaIdid from a search result
providerProvider ID
Terminal window
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"]
}
]

Resolve a direct stream URL for an episode or image URLs for a manga chapter. Pick the translation here (if applicable).

ParamRequiredDescription
unitIdid from a content result
providerProvider ID
languagesub | dub | raw
Terminal window
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.

Inspect subtitle + quality availability for a unit without resolving the playable stream. Useful for populating a subtitle selector before the user hits play.

ParamRequiredDescription
unitIdid from a content result
providerProvider ID
languagesub | dub | raw
Terminal window
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.

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.

Terminal window
curl 'localhost:3000/meta/search?provider=anilist&q=Cowboy%20Bebop'
Terminal window
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.

List episodes/chapters for a meta URN on a specific content provider. Mapping is automatic.

Terminal window
curl 'localhost:3000/meta/content?provider=anilist&id=anilist:1&contentProvider=allmanga'

Resolve a stream / tracks by metadata + episode number:

Terminal window
curl 'localhost:3000/meta/stream?provider=anilist&id=anilist:1&episode=1&contentProvider=allmanga&language=sub'

Trending / popular / seasonal / top from the catalogue:

Terminal window
curl 'localhost:3000/meta/browse?provider=anilist&kind=trending&perPage=10'
curl 'localhost:3000/meta/browse?provider=anilist&kind=seasonal&season=FALL&year=2024'

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.

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.

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

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:

Terminal window
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.

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:

PrefixKey shapeNotes
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.

StatusMeaning
400Missing or unknown query parameter
401Missing or invalid bearer token
404Unknown route
405Non-GET request
500Provider threw an error (message is included in error field)

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.