v1.3.0 · Stable · 11 endpoints · 7 providers

Anime Scraper API

A unified REST API for searching anime catalogs, fetching episode lists, and resolving playable stream URLs across multiple providers. One consistent response shape — no matter which upstream the data came from.

Interactive Playground

Try It Live

Pick an endpoint, fill in the parameters, and hit Send. When you fire a /sources request, a real player spins up below the response so you can watch the stream you just resolved — no separate page, no copy-paste.

Try it live

Fire real requests against this deployment and inspect the response inline.

Interactive

Resolve playable stream URLs (m3u8 + mp4 + iframe). The response feeds straight into the player below.

GET/api/scrape/sources
Quick examples:

Note: anime IDs differ per provider. Search first to find the right ID for your chosen provider. animetsu uses Mongo ObjectIds; miruro/animex use al:{anilistId}; anilight uses al:{anilistId}:{slug}; anipm uses anipm:{seriesId}:{slug}.

Response will appear here after you hit Send.

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.

Providers
7
animetsu · anikuro · animeyubi · miruro · animex · anilight · anipm
Endpoints
11
REST · JSON · cached
Latency
<2s
p50 for full search→play flow

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

Base URLtext
https://your-deployment.example.com/api/scrape

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

Note: If you deploy this publicly without an auth layer, anyone can use your server's bandwidth to proxy video streams. Put it behind a gateway or rate limiter if it's exposed to the internet.

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

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

The solution: Every endpoint that takes an 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.

ID formats accepted by every endpointtext
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:

  1. Fetches the AniList media document for that ID (cached 30 min).
  2. Collects candidate titles: english, romaji, native, synonyms.
  3. Runs the provider's search() with each candidate in priority order.
  4. Picks the first hit (preferring results whose anilistId matches).
  5. 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

Frieren (AniList 154587) on every providerbash
# 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"
Tip: To explicitly inspect what native id the resolver picked (and which title it matched on), hit the /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-Control headers 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 anikuro provider fans out to up to 11 upstreams per /sources call. 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.

GET/api/scrape/providers

Response

200 OKjson
{
  "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.

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.

GET/api/scrape/info?id={animeId}&provider={provider}&enrich={0|1}
ParameterTypeRequiredDefaultDescription
idstringrequiredAnime id from a previous /search response.
providerenumoptionalanimetsuOne of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm.
enrich0 | 1optional1Set 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

200 OK (enriched)json
{
  "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://..." }
    ]
  }
}
If the provider doesn't expose an AniList ID (or if 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.

GET/api/scrape/episodes?id={animeId}&provider={provider}
ParameterTypeRequiredDefaultDescription
idstringrequiredAnime id from /search or /info.
providerenumoptionalanimetsuOne of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm.
Requestbash
curl "https://your-deployment.example.com/api/scrape/episodes?id=al:154587&provider=animetsu"

Response

200 OKjson
[
  {
    "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"]
  }
]
Key field: 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.

GET/api/scrape/servers?id={animeId}&ep={episode}&provider={provider}
ParameterTypeRequiredDefaultDescription
idstringrequiredAnime id.
epnumberrequiredEpisode number (1-indexed).
providerenumoptionalanimetsuOne of: animetsu, anikuro, animeyubi, miruro, animex, anilight, anipm.
Requestbash
curl "https://your-deployment.example.com/api/scrape/servers?id=al:154587&ep=1&provider=animetsu"

Response (per provider)

animetsu
json
[
  { "id": "kite", "label": "kite", "default": true },
  { "id": "gogo", "label": "gogo" },
  { "id": "vidstream", "label": "vidstream" }
]
anikuro
json
[
  { "id": "animeverse", "label": "animeverse", "description": "MP4 — fast", "default": true },
  { "id": "animepahe", "label": "animepahe" },
  { "id": "anikoto", "label": "anikoto", "description": "HLS — multi quality" },
  // ... 8 more upstreams
]
animeyubi
json
[
  { "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.

GET/api/scrape/sources?id={animeId}&ep={episode}&server={server}&type={sub|dub}&provider={provider}
ParameterTypeRequiredDefaultDescription
idstringrequiredAnime id.
epnumberrequiredEpisode number (1-indexed).
serverstringoptionalprovider defaultServer id from /servers, or omit to use the provider's defaultServer.
typeenumoptionalsubAudio variant: sub or dub.
providerenumoptionalanimetsuOne 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

200 OK (animetsu)json
{
  "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=..." }
  ]
}
Source types:
  • 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 with allow="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.

GET/api/scrape/raw?id={animeId}&ep={episode}&server={server}&type={sub|dub}&provider={provider}
ParameterTypeRequiredDefaultDescription
idstringrequiredAnime id.
epnumberrequiredEpisode number.
serverstringoptionalServer id.
typeenumoptionalsubsub or dub.
providerenumoptionalanimetsuanimetsu, anikuro, animeyubi, miruro, animex, anilight, or anipm.
Requestbash
curl "https://your-deployment.example.com/api/scrape/raw?id=14682&ep=1&server=kite&type=sub&provider=animeyubi"

Response shape (varies per provider)

200 OK (animeyubi)json
{
  "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
  }
}
Per-provider raw shape:
  • animetsu — the upstream SourcesResponse object (sources[], subs[], skips).
  • anikuro — returns rawMulti as an object keyed by upstream provider name (animeverse, animepahe, etc.), each containing that provider's raw response.
  • animeyubi — returns a normalized MegaPlay-style payload with hls_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 }.

GET/api/scrape/resolve?anilist={anilistId}&provider={provider}
ParameterTypeRequiredDefaultDescription
anilistnumberrequiredAniList ID (e.g. 154587 for Frieren).
providerenumoptional(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

200 OK (single provider)json
{
  "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",
      "葬送のフリーレン"
    ]
  }
}
Strategy values:
  • passthrough — the provider natively accepts al:{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.

GET/api/scrape/animetsu-id?anilist={anilistId}
ParameterTypeRequiredDefaultDescription
anilistnumberrequiredAniList ID (e.g. 154587 for Frieren).
curl "https://your-deployment.example.com/api/scrape/animetsu-id?anilist=154587"

Response

200 OKjson
{
  "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"
}
When to use this vs. just passing al:{anilistId}

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.

GET/api/scrape/recent?page={page}&per_page={perPage}
ParameterTypeRequiredDefaultDescription
pagenumberoptional1Page number (1-indexed).
per_pagenumberoptional20Results per page (max 50).
Requestbash
curl "https://your-deployment.example.com/api/scrape/recent?page=1&per_page=20"

Response

200 OKjson
{
  "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.

GET/api/scrape/anilist?id={id} | ?search={query} | ?trending=1
ParameterTypeRequiredDefaultDescription
idnumberoptionalAniList media id. Returns a single media object.
searchstringoptionalFree-text search. Returns up to 20 results.
trending1optionalSet to '1' to get the current trending anime list.
curl "https://your-deployment.example.com/api/scrape/anilist?id=154587"
The /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.

GET/api/proxy/m3u8?url={encoded}&format={m3u8|vtt}&referer={encoded}
ParameterTypeRequiredDefaultDescription
urlstring (encoded)requiredThe absolute upstream URL to proxy (URL-encoded).
formatenumoptionalHint the response type: 'm3u8' or 'vtt'. If omitted, the proxy auto-detects.
refererstring (encoded)optionalOverride the Referer header sent to the upstream. Used by anikuro HLS streams.
Direct usage (rare — usually pre-wrapped)bash
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"
How it works:
  1. 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).
  2. If the response is an m3u8 playlist, rewrites every line so segment URLs and #EXT-X-KEY URI tags point back at this proxy.
  3. If the response is a VTT subtitle, returns it with text/vtt content-type.
  4. 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.

types.tstypescript
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 responsejson
{
  "error": "Missing id or ep."
}
StatusMeaningWhen
400Bad RequestMissing required query param.
404Not FoundAnime id doesn't exist on the provider.
502Bad GatewayUpstream provider returned an error or timed out.

Changelog

v1.4.02026-06-27
  • • Added animex provider (animex.one — AniList-native catalog with flixcloud.cc embeds, supports dual-audio for most recent releases).
  • • Reverse-engineered animex's SvelteKit __data.json chunk 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.
v1.3.02026-06-27
  • • Added miruro provider (miruro.to — AniList-native with 7 streaming backends: bonk, ally, pewe, moo, bee, kiwi, hop).
  • • Implements miruro's encrypted /api/secure/pipe protocol (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).
v1.2.02026-06-27
  • • Added animeyubi provider (AnimePahe mirror with kwik.cx iframe embeds).
  • • Added iframe source type for CF-protected embeds.
  • • Added /api/scrape/raw endpoint for upstream response inspection.
  • • Added upstreamReferer field on stream sources.
v1.1.02026-06-15
  • • Added anikuro provider with 11 upstream providers.
  • • AniList enrichment auto-triggered when provider exposes anilistId.
v1.3.02026-06-27
  • • Universal AniList ID routing — every endpoint now accepts al:{anilistId} as the id parameter, resolved automatically per-provider (title-search + 30-min cache).
  • • New /api/scrape/resolve endpoint — 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 /sources response — full upstream JSON, every server, every CDN host, every URL.
v1.0.02026-05-01
  • • Initial release with animetsu provider, CORS proxy, and AniList integration.