Overview
The Animetsu Scraper API is a self-hostable Next.js backend that abstracts four independent anime streaming providers behind a single unified interface. You write one client, point it at this API, and you can swap providers at runtime with a single query parameter — no code changes required.
Each provider wraps a different upstream site (animetsu.live, anikuro.ru, animeyubi.com, miruro.to, animex.one, anilight.live, ani.pm), normalizes its response into the unified TypeScript types documented below, and exposes the raw upstream JSON alongside the normalized data so you can inspect exactly what the provider returned. All HLS / MP4 / subtitle URLs are routed through a built-in CORS proxy so the browser can fetch them directly.
Base URL
All endpoints are relative to your deployment origin. If you're running locally, the base URL is http://localhost:3000. In production, replace it with your deployment URL (Vercel, Fly, your own server, etc.).
https://your-deployment.example.com/api/scrapeEvery documented path below is appended to /api/scrape (or /api/proxy for the streaming proxy).
Authentication
No authentication is required. The API is fully open — anyone with the base URL can call any endpoint. This is by design: the API is meant to be self-hosted behind your own access control (Cloudflare Access, Vercel password protection, a reverse proxy with auth, etc.).
Quick Start
The canonical flow to go from a search query to a playable stream is four requests: search → info → episodes → sources. Pass the anime id from the search response into the next two calls, and the episode number (not id) into the sources call.
A complete flow in JavaScript, from search to playback:
const BASE = "https://your-deployment.example.com/api/scrape";
const provider = "animetsu"; // or "anikuro" | "animeyubi" | "miruro" | "animex" | "anilight" | "anipm"
// UNIVERSAL ROUTING: if you already know the AniList ID (e.g. 154587 =
// Frieren), use "al:154587" as the id on ANY provider — the backend resolves
// it via title search. /search is only needed when you don't know the ID.
const ANILIST_ID = "al:154587";
// 1. (Optional) Search for an anime by title — only if you don't know the AniList ID
const search = await fetch(
`${BASE}/search?q=frieren&provider=${provider}`
).then((r) => r.json());
const anime = search.results[0];
console.log("Found:", anime.title.preferred);
// 2. Fetch full metadata (auto-enriched with AniList data)
const info = await fetch(
`${BASE}/info?id=${ANILIST_ID}&provider=${provider}`
).then((r) => r.json());
console.log("Synopsis:", info.description);
console.log("Episodes:", info.totalEpisodes);
// 3. Get the episode list
const episodes = await fetch(
`${BASE}/episodes?id=${ANILIST_ID}&provider=${provider}`
).then((r) => r.json());
const episode = episodes[0];
console.log("First episode:", episode.number, episode.title);
// 4. Resolve playable stream URLs for episode 1
const sources = await fetch(
`${BASE}/sources?id=${ANILIST_ID}&ep=1&server=kite&type=sub&provider=${provider}`
).then((r) => r.json());
// 5. Drop the master URL into an HLS player
const hls = sources.sources.find((s) => s.isMaster);
console.log("Play this:", hls.url);
// → /api/proxy/m3u8?url=https%3A%2F%2Fswiftstream.top%2F...%2Fmaster.m3u8Universal Routing
The biggest source of confusion in a multi-provider scraper is that every provider has its own native id format. Animetsu uses 24-char Mongo ObjectIds (6989b8a029cf95f4eb03b500); anikuro uses numerics (4231); miruro, animex, and anilight use AniList IDs natively; anipm uses its own composite (anipm:6351:frieren-beyond-...). A user looking at the docs has no idea what to plug in.
id parameter accepts the universal format al:{anilistId}. The backend resolves it to the provider's native id automatically — you never have to know or care what the native id is.Pick any anime on AniList — the numeric id in the URL (e.g. anilist.co/anime/154587) is the AniList ID. Prefix it with al: and you have a universal id that works on every provider.
al:154587 ← universal, works on EVERY provider
al:154587:sousou-no-frieren ← anilight/anipm composite (passthrough)
6989b8a029cf95f4eb03b500 ← animetsu native id (also accepted)
4231 ← anikuro native id (also accepted)
anipm:6351:frieren-beyond-journey-end ← anipm native id (also accepted)How resolution works
When you pass al:154587 on a provider that doesn't natively index by AniList ID (animetsu, anikuro, animeyubi, anipm), the backend:
- Fetches the AniList media document for that ID (cached 30 min).
- Collects candidate titles: english, romaji, native, synonyms.
- Runs the provider's
search()with each candidate in priority order. - Picks the first hit (preferring results whose
anilistIdmatches). - Caches the resolved native id for 30 min so subsequent calls on the same provider+anime are instant.
For providers that natively index by AniList ID (miruro, animex, anilight), the al:{anilistId} form is passed straight through with zero overhead.
The same anime, three providers, one id
# Animetsu (Mongo ObjectId under the hood)
curl ".../api/scrape/sources?id=al:154587&ep=1&provider=animetsu"
# Anikuro (numeric id under the hood)
curl ".../api/scrape/sources?id=al:154587&ep=1&provider=anikuro"
# Ani.pm (anipm:{seriesId}:{slug} under the hood)
curl ".../api/scrape/sources?id=al:154587&ep=1&provider=anipm"
# Miruro (al:154587 is its native format — zero resolution overhead)
curl ".../api/scrape/sources?id=al:154587&ep=1&provider=miruro"/api/scrape/resolve endpoint documented below. It returns the full resolution trace — useful for debugging "why doesn't this anime resolve on provider X".Rate Limits
There are no hard rate limits built in. However, each request fans out to one or more upstream anime sites, which DO rate-limit aggressively. Be a good citizen:
- •Cache responses client-side. The API sets
Cache-Controlheaders ranging from 60s (sources) to 1h (providers list) — respect them. - •Don't hammer search with the same query. If you're building a search-as-you-type UI, debounce by 300ms+.
- •The
anikuroprovider fans out to up to 11 upstreams per/sourcescall. Expect ~3-8s response time on cold cache. - •AniList (used for enrichment) rate-limits to ~90 req/min. The server caches AniList responses for 30 min.
List all providers
Returns the metadata for every registered provider. Use this to populate a provider switcher UI or to discover which providers support dub streams.
/api/scrape/providersResponse
{
"providers": [
{
"id": "animetsu",
"label": "Animetsu",
"description": "Soft sub · Multi quality · Cloudflare-fronted",
"accent": "from-rose-500 to-orange-500",
"supportsDub": true,
"defaultServer": "kite"
},
{
"id": "anikuro",
"label": "Anikuro",
"description": "11 upstream providers · Sub/Dub · AniList IDs native",
"accent": "from-violet-500 to-fuchsia-500",
"supportsDub": true,
"defaultServer": "animeverse"
},
{
"id": "animeyubi",
"label": "Animeyubi",
"description": "AnimePahe mirror · Sub/Dub · Kwik embeds",
"accent": "from-emerald-500 to-teal-500",
"supportsDub": true,
"defaultServer": "kwik-mp4"
},
{
"id": "miruro",
"label": "Miruro",
"description": "AniList-native · 7 streaming providers · Sub/Dub · Skip markers",
"accent": "from-sky-500 to-indigo-500",
"supportsDub": true,
"defaultServer": "bonk"
},
{
"id": "animex",
"label": "Animex",
"description": "AniList-native catalog with flixcloud.cc embeds (sub + dual audio).",
"accent": "from-pink-500 to-rose-500",
"supportsDub": true,
"defaultServer": "flixcloud"
},
{
"id": "anilight",
"label": "Anilight",
"description": "AniList-native catalog · MegaPlay streams · Sub/Dub · Skip markers",
"accent": "from-amber-500 to-orange-500",
"supportsDub": true,
"defaultServer": "megaplay"
},
{
"id": "anipm",
"label": "Ani.pm",
"description": "Ani.pm — Vega MP4 + Onyx HLS + MegaPlay · sub & dub · all servers",
"accent": "from-indigo-500 to-violet-500",
"supportsDub": true,
"defaultServer": "onyx-hls"
}
]
}Cached for 1 hour. Use this endpoint once at app startup.
Search anime
Search a provider's catalog for anime matching a free-text query. Returns a list of unified search results — pick one and pass its id into /info or /episodes.
/api/scrape/search?q={query}&provider={provider}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
q | string | required | — | Free-text search query (anime title). |
provider | enum | optional | animetsu | One of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm. |
curl "https://your-deployment.example.com/api/scrape/search?q=frieren&provider=animetsu"Response (animetsu)
{
"results": [
{
"id": "14682",
"title": {
"romaji": "Sousou no Frieren",
"english": "Frieren: Beyond Journey's End",
"native": "葬送のフリーレン"
},
"coverImage": {
"large": "https://animetsu.live/cdn/...",
"medium": "https://animetsu.live/cdn/...",
"color": "#e8b4b8"
},
"banner": "https://animetsu.live/cdn/banner.jpg",
"description": "Frieren, an elven mage...",
"status": "FINISHED",
"year": 2023,
"format": "TV",
"genres": ["Adventure", "Drama", "Fantasy"],
"averageScore": 89,
"totalEpisodes": 28,
"isAdult": false,
"duration": 24,
"season": "FALL"
}
],
"provider": "animetsu"
}animetsu— does not returnanilistIdin search results, but you can still query it with the universalal:{anilistId}ID format — the backend auto-resolves it via title search.anikuro— returns nativeanilistIdandmalIdfor every result.animeyubi— returns minimal metadata (title + cover image only). Use/infoto get the full document.miruro— AniList-native: returns full AniList metadata (banner, genres, studios, trailer) in search results. IDs are prefixed withal:(e.g.al:154587).animex— AniList-native catalog backed by animex.one with flixcloud.cc embeds. Returns dual-audio (sub+dub) for most recent releases. IDs are prefixed withal:. Server-side m3u8 extraction via WASM + PBKDF2 + AES-CBC when Cloudflare allows; iframe fallback otherwise.anilight— REST API at anilight.live with megaplay.buzz HLS streams. Sub + dub, intro/outro skip markers, VTT subtitles. IDs areal:{anilistId}:{slug}. Cloudflare-bypassed via curl.anipm— wraps ani.pm and scrapes EVERY server the site exposes: Vega (MP4 file server), Onyx (HLS master with 1080p/720p/360p variants), Vidnest (iframe embed), and MegaPlay (HLS via megaplay.buzz). Source order in the response: m3u8 first, MP4 second, iframe last. IDs areanipm:{seriesId}:{slug}.
Get anime info
Fetch the full metadata document for a single anime. If the provider exposes an AniList ID and enrich is enabled (default), the response is automatically merged with AniList data: characters, studios, recommendations, trailer, etc.
/api/scrape/info?id={animeId}&provider={provider}&enrich={0|1}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Anime id from a previous /search response. |
provider | enum | optional | animetsu | One of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm. |
enrich | 0 | 1 | optional | 1 | Set to 0 to skip AniList enrichment (faster, but no characters/studios/recommendations). |
curl "https://your-deployment.example.com/api/scrape/info?id=al:154587&provider=animetsu"Response
{
"id": "14682",
"anilistId": 154587,
"malId": 52991,
"title": {
"romaji": "Sousou no Frieren",
"english": "Frieren: Beyond Journey's End",
"native": "葬送のフリーレン"
},
"coverImage": { "large": "https://..." },
"banner": "https://...",
"description": "Frieren, an elven mage, is a member of the hero party...",
"status": "FINISHED",
"year": 2023,
"format": "TV",
"genres": ["Adventure", "Drama", "Fantasy"],
"averageScore": 89,
"totalEpisodes": 28,
"duration": 24,
"season": "FALL",
"anilist": {
"id": 154587,
"trailer": { "id": "ASLk6aY-B3Q", "site": "youtube" },
"studios": [
{ "id": 1441, "name": "Madhouse", "isAnimationStudio": true }
],
"characters": [
{
"id": 1,
"name": { "full": "Frieren", "native": "フリーレン" },
"image": "https://...",
"role": "MAIN",
"voiceActor": { "name": { "full": "Atsumi Tanezaki" } }
}
],
"recommendations": [
{ "id": 12345, "title": "Vinland Saga", "coverImage": "https://..." }
]
}
}enrich=0), the response omits the anilist field. Returns 404 if the id doesn't exist.Get episode list
Returns the full list of episodes for the given anime, sorted by episode number. Each episode exposes a sourceId that's used internally — you only need the number field for the /sources call.
/api/scrape/episodes?id={animeId}&provider={provider}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Anime id from /search or /info. |
provider | enum | optional | animetsu | One of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm. |
curl "https://your-deployment.example.com/api/scrape/episodes?id=al:154587&provider=animetsu"Response
[
{
"number": 1,
"displayNumber": "1",
"sourceId": "14682",
"title": "The Journey's End",
"description": "After a ten-year journey...",
"thumbnail": "https://...",
"airedAt": "2023-09-29T16:00:00.000Z",
"duration": 24,
"filler": false,
"variants": ["sub", "dub"]
},
{
"number": 2,
"displayNumber": "2",
"sourceId": "14682",
"title": "It Didn't Have to Be a Magic Blast",
"filler": false,
"variants": ["sub", "dub"]
}
]variants tells you which audio variants are available for this episode. Pass type=sub or type=dub to /sources accordingly.Get streaming servers
Returns the list of available streaming servers for a specific episode. Some providers (anikuro) treat each upstream as a 'server'; others (animetsu) have multiple servers per episode. If the provider doesn't implement getServers, returns an empty array.
/api/scrape/servers?id={animeId}&ep={episode}&provider={provider}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Anime id. |
ep | number | required | — | Episode number (1-indexed). |
provider | enum | optional | animetsu | One of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm. |
curl "https://your-deployment.example.com/api/scrape/servers?id=al:154587&ep=1&provider=animetsu"Response (per provider)
[
{ "id": "kite", "label": "kite", "default": true },
{ "id": "gogo", "label": "gogo" },
{ "id": "vidstream", "label": "vidstream" }
][
{ "id": "animeverse", "label": "animeverse", "description": "MP4 — fast", "default": true },
{ "id": "animepahe", "label": "animepahe" },
{ "id": "anikoto", "label": "anikoto", "description": "HLS — multi quality" },
// ... 8 more upstreams
][
{ "id": "kwik-mp4", "label": "Kwik · MP4", "default": true },
{ "id": "kwik-hls", "label": "Kwik · HLS" }
]Pass any of these id values as the server parameter to /sources.
Get stream sources
The main endpoint — resolves playable stream URLs for a specific episode. Returns HLS master playlists, individual quality variants, MP4 files, or iframe embeds (for kwik.cx), all pre-wrapped through the CORS proxy. Also includes subtitles and intro/outro skip markers when the upstream exposes them.
/api/scrape/sources?id={animeId}&ep={episode}&server={server}&type={sub|dub}&provider={provider}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Anime id. |
ep | number | required | — | Episode number (1-indexed). |
server | string | optional | provider default | Server id from /servers, or omit to use the provider's defaultServer. |
type | enum | optional | sub | Audio variant: sub or dub. |
provider | enum | optional | animetsu | One of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm. |
curl "https://your-deployment.example.com/api/scrape/sources?id=al:154587&ep=1&server=kite&type=sub&provider=animetsu"Response
{
"sources": [
{
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fswiftstream.top%2F...%2Fmaster.m3u8",
"type": "master",
"quality": "auto",
"isMaster": true,
"originalUrl": "https://swiftstream.top/.../master.m3u8"
},
{
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fswiftstream.top%2F...%2F1080p.m3u8",
"type": "hls",
"quality": "1080p",
"originalUrl": "https://swiftstream.top/.../1080p.m3u8"
},
{
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fswiftstream.top%2F...%2F720p.m3u8",
"type": "hls",
"quality": "720p"
},
{
"url": "/api/proxy/m3u8?url=https%3A%2F%2Fswiftstream.top%2F...%2F360p.m3u8",
"type": "hls",
"quality": "360p"
}
],
"subtitles": [
{
"url": "/api/proxy/m3u8?format=vtt&url=https%3A%2F%2F...%2Fen.vtt",
"lang": "English"
}
],
"skips": {
"intro": { "start": 5, "end": 95 },
"outro": { "start": 1380, "end": 1440 }
},
"server": "kite",
"provider": "animetsu",
"qualities": [
{ "label": "1080p", "resolution": "1920x1080", "url": "/api/proxy/m3u8?url=..." },
{ "label": "720p", "resolution": "1280x720", "url": "/api/proxy/m3u8?url=..." },
{ "label": "360p", "resolution": "640x360", "url": "/api/proxy/m3u8?url=..." }
]
}master— adaptive HLS playlist (preferred for hls.js / Safari native HLS).hls— single-quality HLS playlist.mp4— direct MP4 file (use in a<video>tag, supports Range).iframe— kwik.cx embed URL. Render in an<iframe>tag withallow="autoplay; fullscreen".
Get raw upstream response
Returns the exact JSON the upstream provider's API returned, before any normalization. Useful for debugging, building provider-specific UIs, or inspecting fields that aren't surfaced in the unified response. Same params as /sources.
/api/scrape/raw?id={animeId}&ep={episode}&server={server}&type={sub|dub}&provider={provider}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Anime id. |
ep | number | required | — | Episode number. |
server | string | optional | — | Server id. |
type | enum | optional | sub | sub or dub. |
provider | enum | optional | animetsu | animetsu, anikuro, animeyubi, miruro, animex, anilight, or anipm. |
curl "https://your-deployment.example.com/api/scrape/raw?id=14682&ep=1&server=kite&type=sub&provider=animeyubi"Response shape (varies per provider)
{
"provider": "animeyubi",
"animeId": "14682",
"episode": 1,
"server": "kwik-mp4",
"streamType": "sub",
"raw": {
"provider": "animeyubi",
"api": "https://animeyubi.com/api/v4/pahe/episodes/12345/",
"animeId": "14682",
"episodeId": "12345",
"episodeNumber": 1,
"streamType": "sub",
"server": "kwik-mp4",
"episode": {
"title": "1",
"id": 12345,
"videos": [
{
"title": "SEV · 1080p BD",
"id": 67890,
"video_type": "mp4",
"url": "https://kwik.cx/f/abc123",
"errors": 0
}
],
"next": { "title": "2", "id": 12346 },
"previous": null
},
"normalized": [
{
"anilist_id": null,
"episode": 1,
"stream_type": "sub",
"provider": "Kwik",
"server_id": 67890,
"cdn_host": "kwik.cx",
"hls_url": null,
"mp4_url": "https://kwik.cx/f/abc123",
"rmvb_url": null,
"stream_format": "mp4",
"quality": "1080p",
"embed_url": "https://kwik.cx/f/abc123",
"video_title": "SEV · 1080p BD",
"errors": 0
}
]
},
"rawMulti": null,
"unified": {
"sources": [ /* ... same shape as /sources ... */ ],
"subtitles": [],
"skips": null,
"qualities": null
}
}animetsu— the upstreamSourcesResponseobject (sources[], subs[], skips).anikuro— returnsrawMultias an object keyed by upstream provider name (animeverse, animepahe, etc.), each containing that provider's raw response.animeyubi— returns a normalized MegaPlay-style payload withhls_url,mp4_url,embed_url,stream_type,cdn_host, etc.
The response always includes a unified field with the same shape as /sources so you can see both raw and normalized side by side.
Resolve AniList ID
Resolves an AniList ID to the provider's native anime id. Useful for figuring out what id to pass to /sources on a given provider, checking whether a provider has a given anime before doing a full /sources lookup, or debugging the universal AniList routing. If `provider` is omitted, resolves across ALL providers in parallel and returns a map of { providerId: ResolveResult | null }.
/api/scrape/resolve?anilist={anilistId}&provider={provider}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
anilist | number | required | — | AniList ID (e.g. 154587 for Frieren). |
provider | enum | optional | (all) | One of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm. If omitted, resolves across all providers in parallel. |
curl "https://your-deployment.example.com/api/scrape/resolve?anilist=154587&provider=animetsu"Response
{
"anilistId": 154587,
"provider": "animetsu",
"anilist": {
"id": 154587,
"idMal": 52991,
"title": {
"romaji": "Sousou no Frieren",
"english": "Frieren: Beyond Journey's End",
"native": "葬送のフリーレン"
},
"synonyms": ["Frieren: Beyond Journey's End"],
"coverImage": { "large": "https://s4.anilist.co/..." },
"seasonYear": 2023,
"format": "TV"
},
"resolved": {
"nativeId": "6989b8a029cf95f4eb03b500",
"anilistId": 154587,
"provider": "animetsu",
"matchedTitle": "Frieren: Beyond Journey's End",
"strategy": "title-search",
"triedTitles": [
"Frieren: Beyond Journey's End",
"Sousou no Frieren",
"葬送のフリーレン"
]
}
}passthrough— the provider natively acceptsal:{id}(miruro, animex, anilight).title-search— the backend ran the provider's search with AniList candidate titles and matched.cache-hit— resolution was served from the in-memory 30-min cache.
Cached for 5 min. When provider is omitted, the response's resolved field is an object keyed by provider id, each value being a ResolveResult or null if the provider doesn't have that anime.
Animetsu ID Finding
Purpose-built resolver for the animetsu provider. Pass an AniList ID, get back the animetsu native Mongo ObjectId (e.g. 6989b8a029cf95f4eb03b500) — the long internal id that animetsu uses for /sources, /episodes, /info. Also returns a ready-to-use /sources URL and the full resolution trace (which AniList title matched). A visual UI for this endpoint lives at /animetsu-id.
/api/scrape/animetsu-id?anilist={anilistId}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
anilist | number | required | — | AniList ID (e.g. 154587 for Frieren). |
curl "https://your-deployment.example.com/api/scrape/animetsu-id?anilist=154587"Response
{
"anilistId": 154587,
"animetsuId": "6989b8a029cf95f4eb03b500",
"matchedTitle": "Frieren: Beyond Journey's End",
"strategy": "title-search",
"triedTitles": [
"Frieren: Beyond Journey's End",
"Sousou no Frieren",
"葬送のフリーレン"
],
"anilist": {
"id": 154587,
"idMal": 52991,
"title": {
"romaji": "Sousou no Frieren",
"english": "Frieren: Beyond Journey's End",
"native": "葬送のフリーレン"
},
"coverImage": { "large": "https://s4.anilist.co/..." },
"seasonYear": 2023,
"format": "TV"
},
"universalId": "al:154587",
"sourcesUrl": "/api/scrape/sources?id=6989b8a029cf95f4eb03b500&provider=animetsu&ep=1",
"universalSourcesUrl": "/api/scrape/sources?id=al%3A154587&provider=animetsu&ep=1"
}In most cases you don't need this endpoint — every other endpoint already accepts al:{anilistId} and resolves internally. Use this endpoint when you want to:
- Cache the animetsu native id client-side to skip resolution on subsequent calls
- Debug why a particular anime isn't resolving (inspect
triedTitles) - Pass the native id to a non-API consumer (e.g. an external script)
- Show users the underlying animetsu id for transparency
Returns 404 if the anime isn't found on animetsu's catalog after trying all candidate titles. The response body includes the AniList metadata and the list of titles that were tried, so you can see why it failed. Cached for 5 min.
Get recent releases
Returns the most recently added anime episodes from the animetsu upstream. Useful for building a 'What's new' landing page. This endpoint is animetsu-only — it does not accept a provider parameter.
/api/scrape/recent?page={page}&per_page={perPage}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | number | optional | 1 | Page number (1-indexed). |
per_page | number | optional | 20 | Results per page (max 50). |
curl "https://your-deployment.example.com/api/scrape/recent?page=1&per_page=20"Response
{
"currentPage": 1,
"perPage": 20,
"hasNextPage": true,
"results": [
{
"id": "14682",
"title": { "romaji": "Sousou no Frieren", "preferred": "Frieren" },
"cover_image": { "large": "https://..." },
"episode": 28,
"aired_at": "2024-03-22T16:00:00.000Z",
"is_dub": false
}
]
}AniList enrichment
Direct passthrough to the AniList GraphQL API, cached for 30 minutes. Use this to fetch rich metadata (characters, studios, recommendations, trailers), search AniList by name, or get the current trending list. Exactly one of id, search, or trending must be provided.
/api/scrape/anilist?id={id} | ?search={query} | ?trending=1| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | number | optional | — | AniList media id. Returns a single media object. |
search | string | optional | — | Free-text search. Returns up to 20 results. |
trending | 1 | optional | — | Set to '1' to get the current trending anime list. |
curl "https://your-deployment.example.com/api/scrape/anilist?id=154587"/info endpoint already calls AniList internally when the provider exposes an anilistId. Use this endpoint only when you need to query AniList directly without going through a provider.CORS proxy for m3u8 / segments / subtitles
The browser cannot directly fetch upstream video URLs due to CORS and Cloudflare. This proxy fetches them server-side with the right Referer/User-Agent headers, rewrites relative URIs in m3u8 playlists so segment requests also go through the proxy, and returns the response with CORS headers wide open. You usually don't call this directly — the /sources endpoint returns pre-wrapped URLs.
/api/proxy/m3u8?url={encoded}&format={m3u8|vtt}&referer={encoded}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url | string (encoded) | required | — | The absolute upstream URL to proxy (URL-encoded). |
format | enum | optional | — | Hint the response type: 'm3u8' or 'vtt'. If omitted, the proxy auto-detects. |
referer | string (encoded) | optional | — | Override the Referer header sent to the upstream. Used by anikuro HLS streams. |
curl "https://your-deployment.example.com/api/proxy/m3u8?url=https%3A%2F%2Fswiftstream.top%2Fmaster.m3u8"
# For anikuro streams that need a custom referer:
curl "https://your-deployment.example.com/api/proxy/m3u8?url=https%3A%2F%2Fcdn.mewstream.buzz%2Fmaster.m3u8&referer=https%3A%2F%2Fanikuro.ru%2F"- Fetches the upstream URL with a browser User-Agent and the appropriate Referer (animetsu.live by default, anikuro.ru if the URL contains anikuro.ru, or your custom referer).
- If the response is an m3u8 playlist, rewrites every line so segment URLs and
#EXT-X-KEY URItags point back at this proxy. - If the response is a VTT subtitle, returns it with
text/vttcontent-type. - Otherwise streams the binary (TS/fMP4) through with
Access-Control-Allow-Origin: *.
TypeScript Types
The unified types every provider maps to. Copy these into your client to get full type safety. The full source is at src/lib/providers/types.ts.
export type ProviderId =
| "animetsu"
| "anikuro"
| "animeyubi"
| "miruro"
| "animex"
| "anilight"
| "anipm";
/**
* Universal ID formats — accepted by EVERY endpoint that takes an `id`.
*
* al:{anilistId} ← universal, works on every provider
* al:{anilistId}:{slug} ← anilight / anipm composite (passthrough)
* {provider-native-id} ← provider's own internal id (also accepted)
*
* Resolution is automatic: pass al:154587 to /sources on ANY provider and
* the backend looks up the AniList title, searches that provider's catalog,
* and resolves the native id for you. See the "Universal Routing" section.
*/
export interface UnifiedSearchResult {
id: string;
anilistId?: number;
malId?: number;
title: {
romaji?: string;
english?: string;
native?: string;
preferred?: string;
};
coverImage?: {
cover?: string;
banner?: string;
large?: string;
medium?: string;
small?: string;
color?: string;
};
banner?: string;
description?: string;
status?: string;
year?: number;
format?: string;
genres?: string[];
averageScore?: number;
totalEpisodes?: number | null;
isAdult?: boolean;
duration?: number;
season?: string;
}
export interface UnifiedEpisode {
number: number;
displayNumber?: string;
sourceId: string;
title?: string;
description?: string;
thumbnail?: string;
image?: string;
airedAt?: string;
duration?: number;
filler?: boolean;
variants?: string[]; // ["sub"] | ["sub", "dub"]
}
export interface UnifiedStreamSource {
/** Proxy-ready URL — drop into an HLS player, <video>, or <iframe> */
url: string;
/** "hls" | "mp4" | "master" | "iframe" */
type: "hls" | "mp4" | "master" | "iframe";
quality?: string;
isMaster?: boolean;
originalUrl?: string;
upstreamReferer?: string;
}
export interface UnifiedSubtitle {
url: string;
lang: string;
}
export interface UnifiedSkipMarkers {
intro?: { start: number; end: number };
outro?: { start: number; end: number };
}
export interface UnifiedSources {
sources: UnifiedStreamSource[];
subtitles: UnifiedSubtitle[];
skips?: UnifiedSkipMarkers;
server: string;
provider: ProviderId;
qualities?: { label: string; resolution: string; url: string }[];
/** Raw upstream payload — only from /api/scrape/raw */
raw?: unknown;
rawMulti?: Record<string, unknown>;
}Errors
All errors follow a consistent JSON shape. HTTP status codes follow REST conventions: 400 for bad input, 404 for not found, 502 for upstream failures.
{
"error": "Missing id or ep."
}| Status | Meaning | When |
|---|---|---|
400 | Bad Request | Missing required query param. |
404 | Not Found | Anime id doesn't exist on the provider. |
502 | Bad Gateway | Upstream provider returned an error or timed out. |
Changelog
- • Added
animexprovider (animex.one — AniList-native catalog with flixcloud.cc embeds, supports dual-audio for most recent releases). - • Reverse-engineered animex's SvelteKit
__data.jsonchunk protocol (devalue format with integer indices into a flat array — implements proper recursive dereferencing). - • Episode discovery probes episodes 1..N in parallel (batch of 12, capped at 24) and skips episodes that don't exist yet.
- • Returns iframe embed URLs for playback (flixcloud uses encrypted stream URLs decrypted client-side via Crypto-JS — server-side extraction would be fragile due to Cloudflare TLS-fingerprint blocking).
- • Updated streaming m3u8 proxy: pipes segments through ReadableStream (no buffering), forwards Range headers, handles CORS preflight, auto-picks Referer per upstream host.
- • Added
miruroprovider (miruro.to — AniList-native with 7 streaming backends: bonk, ally, pewe, moo, bee, kiwi, hop). - • Implements miruro's encrypted
/api/secure/pipeprotocol (base64url envelope + XOR obfuscation + gzip decompression). - • Returns both HLS master playlists (routed through CORS proxy with referer) and iframe embed URLs for CF-protected streams.
- • Raw payload includes normalized MegaPlay-style fields (hls_url, mp4_url, embed_url, cdn_host, referer).
- • Added
animeyubiprovider (AnimePahe mirror with kwik.cx iframe embeds). - • Added
iframesource type for CF-protected embeds. - • Added
/api/scrape/rawendpoint for upstream response inspection. - • Added
upstreamRefererfield on stream sources.
- • Added
anikuroprovider with 11 upstream providers. - • AniList enrichment auto-triggered when provider exposes anilistId.
- • Universal AniList ID routing — every endpoint now accepts
al:{anilistId}as theidparameter, resolved automatically per-provider (title-search + 30-min cache). - • New
/api/scrape/resolveendpoint — explicitly inspect what native id the resolver picked. - • Ani.pm (
anipm) 7th provider — Vega MP4 + Onyx HLS + Vidnest + MegaPlay, all servers scraped, m3u8 first / MP4 second / iframe last. - • Animex (
animex) and Anilight (anilight) providers added — Cloudflare-bypassed HLS via curl + megaplay.buzz pipeline. - • Raw payload now exposed on every
/sourcesresponse — full upstream JSON, every server, every CDN host, every URL.
- • Initial release with
animetsuprovider, CORS proxy, and AniList integration.