Overview
Ani.pm is a React SPA backed by an Express-style REST API at https://ani.pm/api/. Cloudflare fronts the whole origin, so every call needs full browser headers (Sec-Ch-Ua, Sec-Fetch-*, Origin) or Cloudflare returns a 403 managed-challenge page. The provider handles this internally via curl with the full browser header set.
The provider's standout feature is server enumeration: instead of picking one stream and hiding the rest, it returns every server ani.pm exposes, normalized into the unified source list AND exposed verbatim in the raw payload. This lets clients pick the best stream per context (HLS for adaptive quality, MP4 for offline download, iframe for fallback when Cloudflare blocks direct m3u8 fetches).
ID Format
The anipm provider uses a composite id format that encodes both the ani.pm numeric series id (used for /api/anime/series/{id}) and the slug (used for /api/anime/src/servers?title={slug}).
anipm:6351:frieren-beyond-journey-end ← full format (recommended)
anipm:6351 ← series id only (slug looked up via cache)
6351 ← bare numeric (treated as series id)
al:154587 ← universal AniList id (auto-resolved)The universal al:{anilistId} format works on anipm too — the backend looks up the AniList title, searches ani.pm, and matches automatically. Use the ID Finder to inspect what native id the resolver picked.
Server Types
Ani.pm exposes 4 distinct server types per episode. The provider scrapes all of them and exposes each as a separate UnifiedStreamSource entry.
Direct MP4 file server. ani.pm serves ~251MB MP4 files with HTTP Range support — perfect for direct download or <video> playback without HLS. Wrapped through the CORS proxy for CORS headers + Range passthrough.
/api/anime/src/file?t={token}HLS master playlist with 1080p/720p/360p quality variants. The upstream m3u8 uses relative URIs that need rewriting — the proxy handles this automatically. Cloudflare-fronted, needs Referer: https://ani.pm/.
/api/anime/src/hls?t={token}Iframe embed at vidnest.fun. Last-resort fallback — the browser solves any Cloudflare challenge natively. Not proxied; passed through as-is.
https://vidnest.fun/anime/...HLS via megaplay.buzz (same pipeline as the anilight provider). The series doc gives us sub/dub embed URLs for every episode; we probe megaplay's API, extract the m3u8 + subtitles + intro/outro skip markers. Most reliable stream — Cloudflare doesn't challenge megaplay.buzz's CDN.
https://megaplay.buzz/stream/getSourcesNew?id={episode_id}Raw Payload Schema
The raw field on the /sources response contains the full upstream payload — every server ani.pm returned, every URL we resolved, every CDN host we touched. Returned verbatim so developers can inspect exactly what ani.pm gave us.
{
"provider": "anipm",
"api": {
"series": "https://ani.pm/api/anime/series/6351",
"servers": "https://ani.pm/api/anime/src/servers?title=frieren-beyond-journey-end&ep=1",
"megaplay_variants": "https://megaplay.buzz/api/12345",
"megaplay_sources": "https://megaplay.buzz/stream/getSourcesNew?id={episode_id}"
},
"animeId": "anipm:6351:frieren-beyond-journey-end",
"episodeNumber": 1,
"server": "onyx-hls",
"sourceType": "sub",
"seriesId": 6351,
"slug": "frieren-beyond-journey-end",
"upstream_servers": {
"sub": [
{ "provider": "Vega", "kind": "file", "url": "/api/anime/src/file?t=abc...", "priority": 1 },
{ "provider": "Onyx", "kind": "hls", "url": "/api/anime/src/hls?t=def...", "priority": 2 },
{ "provider": "Vidnest","kind": "embed", "url": "https://vidnest.fun/anime/...", "priority": 3 }
],
"dub": [
{ "provider": "Vega", "kind": "file", "url": "/api/anime/src/file?t=ghi..." }
]
},
"servers_scraped": {
"hls": [
{
"url": "https://ani.pm/api/anime/src/hls?t=def...",
"proxied_url": "/api/proxy/m3u8?url=https%3A%2F%2Fani.pm%2F...",
"referer": "https://ani.pm/",
"kind": "anipm-onyx"
},
{
"url": "https://*.nekostream.site/master.m3u8",
"proxied_url": "/api/proxy/m3u8?url=...&referer=https%3A%2F%2Fmegaplay.buzz%2F",
"referer": "https://megaplay.buzz/",
"kind": "megaplay"
}
],
"mp4": [
{
"url": "https://ani.pm/api/anime/src/file?t=abc...",
"proxied_url": "/api/proxy/m3u8?url=...",
"referer": "https://ani.pm/"
}
],
"iframe": [
{
"url": "https://vidnest.fun/anime/...",
"referer": "https://ani.pm/"
},
{
"url": "https://megaplay.buzz/stream/s-2/12345/sub",
"referer": "https://ani.pm/"
}
]
},
"megaplay": {
"embedUrl": "https://megaplay.buzz/stream/s-2/12345/sub",
"variantType": "sub",
"m3u8": "https://*.nekostream.site/master.m3u8",
"proxied_m3u8": "/api/proxy/m3u8?url=...&referer=https%3A%2F%2Fmegaplay.buzz%2F",
"subtitles": [
{ "url": "/api/proxy/m3u8?format=vtt&url=...", "lang": "English" }
],
"intro": { "start": 5, "end": 85 },
"outro": { "start": 1380, "end": 1440 }
},
"normalized": {
"anilist_id": 6351,
"episode": 1,
"stream_type": "sub",
"providers_scraped": [
"Vega/file/sub",
"Onyx/hls/sub",
"Vidnest/embed/sub",
"Vega/file/dub",
"megaplay/hls"
],
"cdn_hosts": [
"ani.pm",
"vidnest.fun",
"*.nekostream.site",
"megaplay.buzz"
],
"source_counts": { "hls": 2, "mp4": 1, "iframe": 2, "total": 5 },
"is_default": true
}
}Search ani.pm catalog
Search ani.pm's anime catalog by free-text query. Returns unified search results with anilistId and malId when ani.pm exposes them (most entries have both).
/api/scrape/search?q={query}&provider=anipm| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
q | string | required | — | Free-text search query (anime title). |
provider | enum | optional | animetsu | Must be 'anipm' for this provider. |
curl "https://your-deployment.example.com/api/scrape/search?q=frieren&provider=anipm"{
"results": [
{
"id": "anipm:6351:frieren-beyond-journey-end",
"anilistId": 154587,
"malId": 52991,
"title": {
"english": "Frieren: Beyond Journey's End",
"native": "葬送のフリーレン",
"preferred": "Frieren: Beyond Journey's End"
},
"coverImage": { "large": "https://ani.pm/cdn/posters/..." },
"year": 2023,
"format": "TV",
"genres": ["Adventure", "Drama", "Fantasy"],
"totalEpisodes": 28
}
],
"provider": "anipm"
}Get anime info
Fetches the full ani.pm series document (GET /api/anime/series/{id}) and returns the unified metadata. Auto-enriched with AniList data when anilistId is exposed.
/api/scrape/info?id={animeId}&provider=anipm| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | anipm:{seriesId}:{slug}, anipm:{seriesId}, or al:{anilistId}. |
provider | enum | optional | animetsu | Must be 'anipm'. |
enrich | 0|1 | optional | 1 | Set to 0 to skip AniList enrichment. |
curl "https://your-deployment.example.com/api/scrape/info?id=al:154587&provider=anipm"Get episode list
Returns the full episode list from the ani.pm series document. Each episode's sourceId encodes the megaplay sub/dub embed URLs (used internally by /sources).
/api/scrape/episodes?id={animeId}&provider=anipmcurl "https://your-deployment.example.com/api/scrape/episodes?id=al:154587&provider=anipm"[
{
"number": 1,
"displayNumber": "1",
"sourceId": "6351|1|https://megaplay.buzz/stream/s-2/12345/sub|https://megaplay.buzz/stream/s-2/12345/dub",
"title": "The Journey's End",
"thumbnail": "https://ani.pm/cdn/episodes/...",
"variants": ["sub", "dub"]
}
]Get streaming servers
Enumerates the servers ani.pm exposes for the episode (via /api/anime/src/servers). Always includes 'megaplay' as a synthetic server because the series doc carries megaplay embed URLs for every episode.
/api/scrape/servers?id={animeId}&ep={episode}&provider=anipmcurl "https://your-deployment.example.com/api/scrape/servers?id=al:154587&ep=1&provider=anipm"[
{ "id": "vega-file-sub", "label": "Vega SUB (file)", "description": "Vega · file · sub", "default": true },
{ "id": "onyx-hls-sub", "label": "Onyx SUB (hls)", "description": "Onyx · hls · sub" },
{ "id": "vidnest-embed-sub","label": "Vidnest SUB (embed)","description": "Vidnest · embed · sub" },
{ "id": "vega-file-dub", "label": "Vega DUB (file)", "description": "Vega · file · dub" },
{ "id": "megaplay", "label": "MegaPlay", "description": "megaplay.buzz HLS — same pipeline as the anilight provider" }
]Get stream sources (ALL servers)
The main endpoint. Returns every server ani.pm exposes — HLS master playlists first, MP4 files second, iframe embeds last. Includes subtitles and intro/outro skip markers from MegaPlay. The raw field contains the full upstream payload (see Raw Payload Schema above).
/api/scrape/sources?id={animeId}&ep={episode}&server={server}&type={sub|dub}&provider=anipm| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | anipm id or al:{anilistId}. |
ep | number | required | — | Episode number (1-indexed). |
server | string | optional | onyx-hls | Server id from /servers (informational — all servers are returned regardless). |
type | enum | optional | sub | sub or dub. When dub is requested but unavailable, falls back to sub. |
provider | enum | optional | animetsu | Must be 'anipm'. |
curl "https://your-deployment.example.com/api/scrape/sources?id=al:154587&ep=1&provider=anipm"{
"sources": [
{
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fmegaplay.buzz%2F...master.m3u8&referer=https%3A%2F%2Fmegaplay.buzz%2F",
"type": "master",
"quality": "auto",
"isMaster": true,
"originalUrl": "https://*.nekostream.site/master.m3u8",
"upstreamReferer": "https://megaplay.buzz/"
},
{
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fani.pm%2Fapi%2Fanime%2Fsrc%2Fhls%3Ft%3Ddef...&referer=https%3A%2F%2Fani.pm%2F",
"type": "master",
"quality": "auto",
"isMaster": true,
"originalUrl": "https://ani.pm/api/anime/src/hls?t=def...",
"upstreamReferer": "https://ani.pm/"
},
{
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fani.pm%2Fapi%2Fanime%2Fsrc%2Ffile%3Ft%3Dabc...&referer=https%3A%2F%2Fani.pm%2F",
"type": "mp4",
"quality": "1080p",
"originalUrl": "https://ani.pm/api/anime/src/file?t=abc...",
"upstreamReferer": "https://ani.pm/"
},
{
"url": "https://vidnest.fun/anime/...",
"type": "iframe",
"quality": "auto",
"originalUrl": "https://vidnest.fun/anime/...",
"upstreamReferer": "https://ani.pm/"
}
],
"subtitles": [
{ "url": "/api/proxy/m3u8?format=vtt&url=...", "lang": "English" }
],
"skips": { "intro": { "start": 5, "end": 85 }, "outro": { "start": 1380, "end": 1440 } },
"server": "onyx-hls",
"provider": "anipm",
"raw": { /* see Raw Payload Schema */ }
}Get raw upstream payload only
Returns ONLY the raw upstream payload — the original JSON from ani.pm's API, before any normalization. Useful when you want to inspect exactly what ani.pm returned without the unified source wrapping. Does NOT probe the upstream servers — just records what ani.pm's API returned.
/api/scrape/raw?id={animeId}&ep={episode}&provider=anipmcurl "https://your-deployment.example.com/api/scrape/raw?id=al:154587&ep=1&provider=anipm"Response shape: { provider, animeId, requestedId, episode, server, streamType, raw, rawMulti, unified }. The raw field matches the schema documented in the Raw Payload Schema section above. The unified field also includes the player-ready sources for convenience. For a DEEP scrape that actually probes every server URL, use /api/scrape/anipm-raw instead.
Deep raw scrape — probe EVERY server
The headline endpoint. For every server anipm exposes on the episode, we actually fetch the upstream URL and return what came back. HLS playlists are pulled and parsed (variant count, segment count, total duration, raw m3u8 text), MP4 files are HEAD-probed for Content-Length + Accept-Ranges + Last-Modified, iframe URLs are recorded as-is. No "if it plays or not" gating, no iframe-only cop-out. Per user instruction: "make scrape that scrape all server raw data all server not iframe shit".
/api/scrape/anipm-raw?id={animeId}&ep={episode}&type={sub|dub}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | anipm:{seriesId}:{slug}, anipm:{seriesId}, or al:{anilistId} (universal — resolved via title search). |
ep | number | required | — | Episode number (1-indexed). |
type | enum | optional | sub | sub or dub. When dub is requested but unavailable, falls back to sub for the megaplay variant. |
server | string | optional | — | Optional — accepted but ignored. We probe ALL servers regardless of this parameter. |
curl "https://your-deployment.example.com/api/scrape/anipm-raw?id=al:154587&ep=1" | jq '.servers_grouped.counts'Per-server probe shape
Each entry in the servers array has a different shape depending on its type. The common fields are index, type, kind, url (proxied), upstream_url, upstream_referer, quality. The type-specific fields are:
| Type | Kind | Extra fields returned |
|---|---|---|
| master | anipm-onyx-hls, megaplay-hls | http_status, content_type, raw_m3u8, raw_m3u8_truncated, is_master, variant_count, segment_count, duration_seconds, first_variant_url, first_segment_url |
| mp4 | file | http_status, content_type, content_length, accept_ranges, last_modified, etag |
| iframe | iframe | note (iframe URLs are not probed server-side — the user's browser solves any Cloudflare challenge natively) |
Response example (truncated)
{
"provider": "anipm",
"endpoint": "/api/scrape/anipm-raw",
"description": "Deep raw scrape — every server probed, m3u8 content fetched, MP4 headers captured, iframe URLs recorded. Not just iframe shit.",
"requestedId": "al:154587",
"animeId": "anipm:6351:",
"episode": 1,
"streamType": "sub",
"server": "auto",
"servers": [
{
"index": 0,
"type": "master",
"kind": "megaplay-hls",
"url": "/api/proxy/m3u8?url=https%3A%2F%2F9hjkrt.nekostream.site%2F...master.m3u8&referer=https%3A%2F%2Fmegaplay.buzz%2F",
"upstream_url": "https://9hjkrt.nekostream.site/bb6d2babd7797d94d8f4a8600bc9b44e/.../master.m3u8",
"upstream_referer": "https://megaplay.buzz/",
"quality": "auto",
"http_status": 200,
"content_type": "application/vnd.apple.mpegurl",
"raw_m3u8": "#EXTM3U\n#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1800112,RESOLUTION=1920x1080,FRAME-RATE=25.000,CODECS=\"avc1.640032,mp4a.40.2\"\nindex-f1-v1-a1.m3u8\n#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=49071,...",
"raw_m3u8_truncated": false,
"is_master": true,
"variant_count": 1,
"segment_count": 0,
"duration_seconds": null,
"first_variant_url": "https://9hjkrt.nekostream.site/.../index-f1-v1-a1.m3u8",
"first_segment_url": null
},
{
"index": 1,
"type": "master",
"kind": "anipm-onyx-hls",
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fani.pm%2Fapi%2Fanime%2Fsrc%2Fhls%3Ft%3D...&referer=https%3A%2F%2Fani.pm%2F",
"upstream_url": "https://ani.pm/api/anime/src/hls?t=0xfi2w1aVS37BzKE75nBnjxfUCPZEvN_6yPRZQjmRFfGARxKHBKcveX8BFZ",
"upstream_referer": "https://ani.pm/",
"http_status": 200,
"content_type": "application/vnd.apple.mpegurl; charset=utf-8",
"is_master": true,
"variant_count": 3,
"segment_count": 0,
"first_variant_url": "https://ani.pm/api/anime/src/hls?t=4_oxzZk-6LniwRnaZuyX3VXmsDxH51vNLWz_B3CVa-LilSEZ1RP_pzQ"
},
{
"index": 3,
"type": "mp4",
"kind": "file",
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fani.pm%2Fapi%2Fanime%2Fsrc%2Ffile%3Ft%3D...&referer=https%3A%2F%2Fani.pm%2F",
"upstream_url": "https://ani.pm/api/anime/src/file?t=5snl9KU5Z5Wn5-tfNtRPKdxsHY4wdF6U1UVUlloyA-cQBYRmAiKc2BHTLt",
"upstream_referer": "https://ani.pm/",
"quality": "1080p",
"http_status": 200,
"content_type": "video/mp4",
"content_length": 251568229,
"accept_ranges": "bytes",
"last_modified": "Mon, 15 Jan 2024 09:23:41 GMT",
"etag": "\"abc123\""
},
{
"index": 4,
"type": "iframe",
"kind": "iframe",
"url": "https://vidnest.fun/anime/52991/1/sub",
"upstream_url": "https://vidnest.fun/anime/52991/1/sub",
"upstream_referer": "https://ani.pm/",
"note": "iframe — browser solves Cloudflare challenge natively; not probed server-side"
}
],
"servers_grouped": {
"hls": [ /* ...3 entries... */ ],
"mp4": [ /* ...1 entry... */ ],
"iframe":[ /* ...2 entries.. */ ],
"counts": { "hls": 3, "mp4": 1, "iframe": 2, "total": 6 }
},
"upstream_servers": {
"sub": [
{ "provider": "Vega", "kind": "file", "url": "/api/anime/src/file?t=...", "priority": 1 },
{ "provider": "Onyx", "kind": "hls", "url": "/api/anime/src/hls?t=...", "priority": 2 },
{ "provider": "Vega", "kind": "embed", "url": "https://vidnest.fun/anime/52991/1/sub" }
],
"dub": [
{ "provider": "Onyx", "kind": "hls", "url": "/api/anime/src/hls?t=..." }
]
},
"megaplay": {
"embedUrl": "https://megaplay.buzz/stream/s-2/107257/sub",
"variantType": "sub",
"m3u8": "https://9hjkrt.nekostream.site/.../master.m3u8",
"proxied_m3u8": "/api/proxy/m3u8?url=...&referer=https%3A%2F%2Fmegaplay.buzz%2F",
"subtitles": [ { "url": "/api/proxy/m3u8?format=vtt&url=...", "lang": "English" } ],
"intro": { "start": 5, "end": 85 },
"outro": { "start": 1380, "end": 1440 }
},
"subtitles": [ /* same as megaplay.subtitles */ ],
"skips": { "intro": { "start": 5, "end": 85 }, "outro": { "start": 1380, "end": 1440 } },
"unified_sources": [ /* same shape as /api/scrape/sources sources[] */ ],
"api": {
"series": "https://ani.pm/api/anime/series/6351",
"servers": "https://ani.pm/api/anime/src/servers?title=frieren-beyond-journey-s-end-c6fbj&ep=1",
"megaplay_variants": "https://megaplay.buzz/api/107257",
"megaplay_sources": "https://megaplay.buzz/stream/getSourcesNew?id={episode_id}"
}
}/api/scrape/raw endpoint returns the raw upstream payload, but it doesn't actually probe the server URLs — it just records what ani.pm's API returned. This endpoint goes one level deeper: for every HLS URL, we fetch the m3u8 and parse it. For every MP4 URL, we HEAD-probe it. So you can see at a glance "this Onyx master has 3 variants (1080p, 720p, 360p)" or "this Vega MP4 is 239MB and supports Range requests" — without having to fetch each URL yourself.Endpoint Comparison
Three endpoints touch stream data. Here's exactly what each one returns so you can pick the right one for your use case.
| Endpoint | Probes servers? | Returns raw m3u8? | Returns MP4 headers? | Best for |
|---|---|---|---|---|
| /api/scrape/sources | No | No (URLs only) | No | Player playback — returns proxied URLs ready for <video> / hls.js |
| /api/scrape/raw | No | No (URLs only) | No | Inspecting what ani.pm's API returned (server list, megaplay diagnostics) |
| /api/scrape/anipm-raw | YES | YES | YES | Deep inspection — every server actually fetched, m3u8 parsed, MP4 HEAD-probed |
/sources and /raw both complete in ~1-2s (one round-trip to ani.pm + megaplay). /anipm-raw takes ~3-6s because it fetches every HLS playlist + HEAD-probes every MP4 in parallel. Don't call it on every page load — call it once when you need to inspect what's available, then cache.Upstream API
The provider wraps these ani.pm REST endpoints. All are Cloudflare-fronted GET requests; the provider uses curl with full browser headers (Sec-Ch-Ua, Sec-Fetch-*, Origin) to bypass the managed challenge.
| Endpoint | Purpose | Returns |
|---|---|---|
| /api/anime/search?q= | Search catalog | { items: AnipmSearchItem[] } |
| /api/anime/series/{id} | Full anime doc | AnipmSeries (incl. episodes[]) |
| /api/anime/src/servers?title={slug}&ep={n} | Enumerate servers | { sub: [...], dub: [...] } |
| /api/anime/src/hls?t= | Onyx HLS master | m3u8 (relative URIs!) |
| /api/anime/src/file?t= | Vega MP4 file | 251MB MP4 (Range-supported) |
| megaplay.buzz/api/{realid} | MegaPlay variants | { success, data: MegaplayVariant[] } |
| megaplay.buzz/stream/getSourcesNew?id= | MegaPlay HLS + subs + skips | { sources: {file}, tracks, intro, outro } |
CDN Hosts
The provider touches these CDN hosts. All non-iframe URLs are wrapped through the CORS proxy with the appropriate Referer header.
ani.pmhttps://ani.pm/vidnest.fun(passthrough — browser solves challenge)megaplay.buzzhttps://ani.pm/*.nekostream.sitehttps://megaplay.buzz/Cloudflare Bypass
Ani.pm is fully behind Cloudflare's managed challenge. Node's undici (used by fetch()) gets a 403 on the series/{id} and src/hls endpoints due to TLS fingerprinting. The provider works around this by shelling out to curl with the full browser header set:
curl -sSL \
-A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" \
-H "Referer: https://ani.pm/" \
-H "Origin: https://ani.pm" \
-H "Accept: application/json,text/plain,*/*;q=0.8" \
-H "Accept-Language: en-US,en;q=0.9" \
-H 'Sec-Ch-Ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \
-H "Sec-Ch-Ua-Mobile: ?0" \
-H 'Sec-Ch-Ua-Platform: "Windows"' \
-H "Sec-Fetch-Dest: empty" \
-H "Sec-Fetch-Mode: cors" \
-H "Sec-Fetch-Site: same-origin" \
--http2 \
--max-time 20 \
"https://ani.pm/api/anime/series/6351"The MegaPlay endpoints (megaplay.buzz/api/* and megaplay.buzz/stream/getSourcesNew) do not need curl — Node's fetch works fine because Cloudflare doesn't challenge megaplay.buzz's CDN. The provider uses fetch for those, which is faster.