Files
tubular/docs/miruro-api.md
T

34 KiB
Raw Blame History

Miruro API research

Observed 2026-10-12, scoped to https://barelystarted.miruro.tv. These are reverse-engineering notes for a possible in-repository wrapper, not an official API specification. Tubular does not yet implement Miruro support.

Evidence labels:

  • Verified: an actual HTTP response obtained through the research JavaScript fetch transport, with bounded timeouts and no supplied authentication, cookies, or protection bypass.
  • Frontend: behavior read from deployed first-party JavaScript; not necessarily a server-side guarantee.
  • Unknown: not established by those sources or requests.

Transport limitation: research requests returned JSON, but a local curl request to the same source-validation URL returned 403 text/html, a Cloudflare block page. Native-wrapper access is therefore unproven. Inspect HTTP status and Content-Type before decoding; stop on protection pages rather than treating them as empty results or adding bypass machinery. Successful research requests do not establish cross-client accessibility.

Endpoint map

All paths below are relative to the scoped base URL. Each row was live-verified unless explicitly labeled Frontend. Route-data responses require the SvelteKit decoder, even when Content-Type is application/json.

Purpose GET path and parameters Response
Autocomplete /api/search/browse?q=<query>&limit=8&type=ANIME Plain JSON {data, next_cursor, has_more}
Full search / browse /search/__data.json?q=<query>&cursor=<returned cursor> Route data: {items, entityItems, nextCursor, hasMore, filters, …}
Detail / episode metadata /info/<id>/<slug>/__data.json Streamed route data: detail, promised episodes and related metadata
Watch metadata + initial sources /watch/<id>/<slug>/__data.json?ep=<number> Streamed route data: media, promised episodesData and sourcesData
Fresh episode sources /api/sources?id=<id>&n=<number> Plain JSON {episodeNumber, tracks, config, providerOrder}
Home /__data.json Streamed route data: hero, promised rails and sideLists
Trending /trending/__data.json Route data: {items, requestedPage, year, season, years, seasons}
Schedule page /schedule/__data.json Route data: {entries, weekStart, weekOffset, now, track}
Schedule events /api/schedule?startAt=<Unix seconds>&track=all Plain JSON event array
Genre/tag references /api/discover/refs Plain JSON {genres, tags}
Discover page /discover/__data.json Route data: {lens, lensOffset, spotlights, picks, lensCount}
Personalized discover feed /discover/feed Plain JSON {spotlights, picks}; empty arrays verified without seeds

Sources: route map, search/layout, detail, watch, home, trending, schedule, discover, and the live URLs in the verification ledger. No plain JSON detail/episodes endpoint was identified in those bundles; none was guessed or probed. This is not proof that none exists. Other Miruro domains and third-party /api/secure/pipe wrappers are outside this contract.

IDs and the search-to-stream flow

Use Miruro's opaque catalog ID, not an external numeric ID:

Miruro id:  OERe_a0YS4h-DlsBDOeXwX7HOO6316tw
AniList:    195516
MAL:        61987
Slug:       the-apothecary-diaries-season-3

Verified search records expose external_ids as a map of string arrays, for example {"anilist":["195516"],"mal":["61987"]}. Unmapped provider keys may be absent. Watch data separately exposes numeric anilistId; media.id remains the opaque Miruro ID. Numeric-ID reverse lookup and route aliases are unknown.

The minimum evidenced flow is:

  1. Search and retain the opaque items[].id / data[].id, title, and artwork.
  2. Fetch detail route data and resolve its episodes chunk, or fetch watch route data and resolve episodesData.
  3. Select an actual returned episode number. Detail episode_number:1 mapped to absolute_number:49; source query uses 1, not 49 or an array index.
  4. Fetch /api/sources?id=<opaque id>&n=<selected number> immediately before playback.
  5. Choose a returned track/provider/server with direct streams; preserve that server's headers and runtime configuration. Iframe-only servers are not native media URLs.

Frontend: link helpers generate /info/<id>/<slug> and /watch/<id>/<slug>?ep=<number>. The canonical title is the shorter trimmed English/Romaji title when both exist, otherwise the available English/Romaji, then native title/ID. Slugging applies Unicode NFD, removes combining accents, lowercases, removes non-ASCII-alphanumeric/non-whitespace/non-hyphen characters, trims, hyphenates whitespace, and collapses repeated hyphens. Display-language preference is separate. Missing/wrong-slug behavior is untested; preserve the deployed helper's convention rather than assuming an arbitrary slug works.

Search and discovery contracts

Plain autocomplete

Verified: this request returned eight summary records, next_cursor and has_more:true. Frontend: layout trims q, sends literal limit=8&type=ANIME, debounces input 300 ms and aborts older requests. Empty input clears local suggestions without a request.

An abbreviated observed result, with additional fields omitted:

{
  "data": [{
    "id": "OERe_a0YS4h-DlsBDOeXwX7HOO6316tw",
    "external_ids": {"anilist": ["195516"], "mal": ["61987"]},
    "title": {
      "romaji": "Kusuriya no Hitorigoto 3rd Season",
      "english": "The Apothecary Diaries Season 3",
      "native": "薬屋のひとりごと 第3期"
    },
    "format": "TV",
    "status": "RELEASING",
    "episode_count": 12,
    "episode_counts": {"raw": 2, "sub": 2, "dub": null},
    "episode_duration_minutes": 24,
    "season": "FALL",
    "season_year": 2026,
    "average_score": 85
  }],
  "next_cursor": "<redacted returned cursor>",
  "has_more": true
}

Summary fields also observed: description, cover_url, cover_color, banner_url, background_url, logo_url, source, country_of_origin, popularity, is_adult, next_airing:{episode_number,air_at}, show_cluster_id, show_cluster_position. Across records, English title, counts, duration, season/year, score, colors/artwork, next airing, and cluster fields could be null. Null is not zero or an empty string.

Pagination trap: sending this response's next_cursor as cursor on the same otherwise-valid autocomplete request returned 400, detail:"Unsupported browse request.". /api/search/browse?limit=2&type=ANIME also returned 400, but that probe changed both limit and omission of q, so it does not isolate the cause. Do not promise plain-API pagination or infer supported limits from these probes.

Full search, filters, and pagination

Verified: decoded /search/__data.json?q=apothecary yielded:

{
  mode: "anime", items: MediaSummary[], entityItems: [],
  nextCursor: null, hasMore: false, limit: 24, cursor: "",
  entityPage: 1, filters: { ...string-valued fields... },
  context: null, error: null
}

It returned eleven summaries. An unmatched query returned items:[], entityItems:[], nextCursor:null, hasMore:false, error:null, HTTP 200. Empty results are distinct from errors.

Unfiltered /search/__data.json returned 24 summaries and a non-null nextCursor; a subsequent request with that token as cursor returned another 24 summaries with a different first ID, echoed cursor, and hasMore:true. Retain the returned token opaquely and URL encode it. Token lifetime, binding to filters/cookies and invalid-token handling are unknown. Do not reuse the autocomplete cursor.

Frontend: search page and route dependency metadata expose these page query keys:

Keys Representation / behavior
mode anime, studio, staff, character
q Trimmed query; entity input has a client-only 200-character limit
cursor Anime pagination; reset when filters change
page Numeric entity pagination, not anime pagination
studios, staff, characters Numeric entity-ID context filters
genres, format, status, tracks, season, source, country Single or comma-separated selected values; genre names, not reference IDs
adult Empty/unset is labeled Non-adult; true is labeled Adult
yearFrom, yearTo, minScore, maxScore String-valued range bounds; score slider 0–100
sort One frontend option string

Frontend option values (source), not guaranteed server enums:

  • format: TV, TV_SHORT, MOVIE, OVA, ONA, SPECIAL, MUSIC.
  • status: RELEASING, FINISHED, NOT_YET_RELEASED, HIATUS, CANCELLED.
  • tracks: raw (Sub / Raw), dub (Dub), sub (Scheduled Subs). These discovery labels are not the playback API's ssub/sub classification.
  • season: WINTER, SPRING, SUMMER, FALL; country: JP, KR, CN, TW.
  • source: ORIGINAL, MANGA, LIGHT_NOVEL, WEB_NOVEL, NOVEL, VISUAL_NOVEL, VIDEO_GAME, GAME, DOUJINSHI, ANIME, COMIC, LIVE_ACTION, MULTIMEDIA_PROJECT, PICTURE_BOOK, OTHER.
  • sort: empty (Most Popular), -score, -season_year, season_year, title, -title, -episode_count, episode_count, -duration, duration, -started_on, -ended_on, -updated_at.

The grid stores hestia-page-size in a cookie, computing 24 or 12/15/18/21 by layout. No full-search query limit was observed; 24 is a sampled response, not a guaranteed default. Entity responses/filter semantics, cookie acceptance/ranges and server validation were not exercised.

Other discovery surfaces

Verified, with frontend parameter behavior noted separately:

  • Home: decoded {hero, now, rails:Promise, sideLists:Promise}. Hero records are camelCase (externalIds, coverUrl, backdropUrl, episodeCounts, etc.), unlike search summaries. rails resolves raw/dub latest entries with animeId, episode, track, Unix-second airingAt; sideLists has airing/upcoming summary arrays. Use animeId, not an event ID, for sources.
  • Trending: {items, requestedPage, year, season, years, seasons}. Frontend year/season change the loaded collection; page slices it locally into 18-item pages. It is not evidence of server-side cursor pagination.
  • Schedule page: {entries, weekStart, weekOffset, now, track}; dependency metadata reads week and track. Plain schedule returns [{id,airingAt,episode,track,media}]; startAt and airingAt are Unix seconds. Use media.id, not event id, for sources. Frontend fetches track=all, shifts weeks by 7*86400, and filters all/sub/dub locally. API sub/dub variants are untested; returned entries included track:"raw".
  • References: /api/discover/refs returns genres:[{id,name}] and tags:[{id,name,category,parent_id,parent_name}]; parent fields may be null. Search uses genre names. A tag filter query was not observed in full-search controls.
  • Discover page: {lens:{id,label,tagline,recipe}, lensOffset, spotlights, picks, lensCount}. Spotlights are camelCase; picks use snake_case summaries. Dependency metadata reads season, year, cursor, d; Frontend refresh sets a random d. These are not a proven paginated API.
  • Personalized feed: unseeded /discover/feed returned empty arrays. Frontend sends seeds=<comma-separated numeric list IDs>&exclude=<comma-separated IDs>&provider=<list provider>, using up to eight seeds and fallback provider anilist. These IDs belong to the external list service, not Miruro's opaque catalog. The UI gates personalization on sign-in; seeded-request authentication and schema are unknown.

Detail, episodes, and watch data

Verified: detail route initially decodes to:

{
  detail: MediaDetail, id, currentSlug, tvdbBackgrounds,
  characters: Promise, relations: Promise, recommendations: Promise,
  artworks: Promise, show: Promise, episodes: Promise
}

detail extends summary metadata with alternate_titles:[{value,kind,language}], started_on, ended_on, broadcast, release_schedules, mal_score, mal_members, aired_season, trailer_youtube_id, genres, tags:[{name,rank,category,is_spoiler}], studios:[{id,name,is_main,is_animation_studio}], franchise, updated_at. Dates and some nested identifiers/counts can be null. Broadcast/release schedules describe airing metadata, not stream provider URLs or playback availability.

The sample title's resolved episodes contained two records despite a planned episode_count:12. Abbreviated episode with all observed keys:

{
  "episode_number": 1, "absolute_number": 49, "aired_season_number": 3,
  "kind": "regular", "canon_type": null, "title": "Locusts",
  "synopsis": "A locust plague is looming, and mysteriously, a book on insects belonging to the Shi clan appears.",
  "thumbnail_url": "https://image.tmdb.org/t/p/original/v2k4Nj11mnVwyUhqktOmYr3E4yV.jpg",
  "aired_on": "2026-10-02", "duration_seconds": 1500, "finale_type": null,
  "skip_times": [
    {"kind":"op","start_seconds":63.313,"end_seconds":153.237},
    {"kind":"ed","start_seconds":1335.252,"end_seconds":1425.384}
  ]
}

Do not synthesize playable episodes from planned counts. These records do not carry per-track availability flags; request actual sources for the selected episode. Null dub count does not conclusively prove no playable dub.

Watch route initially has {media, episodesData:Promise, sourcesData:Promise, anilistId, externalIds, episodeNumber, tvdbBackgrounds}. media is adapted camelCase: episodeCounts, duration (minutes), seasonYear, startDate, coverImage, nextAiringEpisode, etc.; it is not interchangeable with detail.

Resolved watch episodesData has:

{
  episodes: [{id:"1",number:1,title,image,airDate,duration:1500,description,filler:false}, ...],
  currentEpisode: <episode 1>, prevEpisode: null, nextEpisode: <episode 2>,
  aniskip: [
    {episode:1,type:"op",start:63.313,end:153.237},
    {episode:1,type:"ed",start:1335.252,end:1425.384}
  ]
}

Watch episodes[].id is the number rendered as a string, not a provider episode ID. aniskip times are seconds; they differ in naming from detail skip_times. /api/sources does not carry those fields. Frontend: watch converts op/mixed-op to intro and ed/mixed-ed to outro, filters wrong episodes/invalid intervals and optional provider/vote/duration constraints, then recomputes skips using player-reported duration. No separate skip endpoint was found. Missing/invalid episode behavior and last-episode boundaries are untested.

Source-resolution and playback contract

Plain source response

Verified: fresh sources returned the following observed structure. Fields seen only as null have unknown non-null types; this is not an exhaustive guaranteed schema.

Sources { episodeNumber:number, tracks:Track[], config:{provider:ProviderConfig}, providerOrder:string[] }
Track { track:string, providers:Provider[] }
Provider { provider:string, servers:Server[], subtitles:Subtitle[], thumbnails:null|{vtt:string,sprite:null}, downloads:Download[] }
Server { server:string, headers:null|{header:string}, embed:null|{url:string}, streams:Stream[] }
Stream { url:string, format:string, quality:null, resolution:null, codec:null, embed:null }
Subtitle { language:string, label:string, file:string, format:string, default:boolean }
Download { url:string, quality:null, size:null, fansub:null }
  • Sample tracks were ssub (soft subtitles) and sub (hard-subtitled, UI h-sub); Frontend also supports dub. No separate audio property was observed. Discovery raw/sub/dub counts are a different vocabulary.
  • Sample direct streams used format:"hls". Frontend recognizes HLS strings/MIME types, DASH/MPD, MP4, WebM and Ogg, and defaults a missing format to HLS. Those are consumer inputs, not verified server formats.
  • Quality/resolution/codec were null, yet a fetched master advertised RESOLUTION=1920x1080; do not equate null metadata with absent HLS renditions.
  • An iframe-only server (bun) had streams:[], headers:null, embed:{url}. An embed page is not a native stream.
  • Subtitle language strings were provider-specific (en/eng, sp, po); distinct Spanish labels shared language:"sp". Preserve labels, not only language codes. VTT was verified; SRT support is frontend-only.
  • Subtitle/thumbnail URLs can end in .jpeg while returning text/vtt. Classify by format/content, not extension. Relative thumbnail sprite references must be resolved against the VTT URL.
  • Download URLs can be download pages; their being raw media files is unproven.
  • Source URLs may contain tokens. Fetch fresh before playing; do not persist tokens as catalog identity or log full URLs. Expiry and IP binding are unknown.

A sanitized projection (URL values deliberately redacted):

{
  "episodeNumber": 1,
  "tracks": [{"track":"ssub","providers":[{
    "provider":"sun",
    "servers":[{
      "server":"icarus-2-1",
      "headers":{"Origin":"https://ultracloud.cc","Referer":"https://ultracloud.cc/"},
      "embed":null,
      "streams":[{"url":"<fresh HLS URL>","format":"hls","quality":null,"resolution":null,"codec":null,"embed":null}]
    }],
    "subtitles":[{"language":"en","label":"English","file":"<fresh VTT URL>","format":"vtt","default":true}],
    "thumbnails":{"vtt":"<fresh thumbnail VTT URL>","sprite":null}
  }]}]
}

This projects one server/provider and omits config, other tracks/providers and downloads; it is not a complete response. Preserve the actual returned header strings without normalization.

Runtime config and selection

Verified: response config entries contain capabilities:{sub,ssub,download,skip_times,thumbnails}, visible, player (native/iframe observed), parent, proxy, variantOrder, cors, fallback, and optional hls / downloadWorker fields. proxy was false or {rotate:boolean,segments:boolean}; hls contained segments:{enabled,origin} and query:{enabled,params}. Non-null fallback shape is unknown.

Observed providerOrder: sun, bee, pewe, hop, ally, kiwi, moo, bonk, knob, nun, bun, cog, twin, telli. Config includes providers absent from the selected episode. Treat names/order as runtime data. Capability flags and cors were false even where streams/subtitles/downloads/thumbnails were returned; they are not verified availability gates.

Frontend (watch client):

  1. Use saved sub/dub and provider preferences. For sub, consult each provider's variantOrder for sub/ssub, then append missing variants; iterate providerOrder and retain only actual provider/track combinations.
  2. Prefer exact provider:track, then same provider, then first available. If a requested language has no entries, try the opposite language. This is existing UI behavior, not a recommendation to silently change a native user's language.
  3. Flatten selected provider servers' nonempty streams in returned order; start at the first and advance on failure. Retain iframe embeds only for player:"iframe".
  4. Browser stream projection extracts Referer case-insensitively, but raw API also returns Origin. A native wrapper should retain the entire header map.

Direct resources, proxies, and browser player

Verified: using fresh Sun stream/subtitle/thumbnail URLs and the server's Origin/Referer headers, research GETs obtained an HLS master (200 application/vnd.apple.mpegurl) and two VTT bodies (200 text/vtt). No nested rendition playlist, media segment, key, sprite, download page or player was exercised. Whether either header is mandatory was not tested. This proves resource retrieval, not full playback.

Frontend: deployment HTML environment identifies https://strm.cx, proxy bases https://s1.keeply.top/ and https://s2.keeply.top/, and a public proxy obfuscation key. Watch source shows:

  • Sun uses direct streams (proxy:false in the sample). Bee/Hop use {rotate:false,segments:true}; a bare direct URL is not proven equivalent for these providers.
  • Browser proxy paths encode URL/Referer and use /pl.m3u8, /sub.vtt, /seg.jpg. This is a private deployment convention, not /api/secure/pipe. No replay of that obfuscation protocol was needed for direct-resource verification.
  • Provider hls overrides can rewrite segment/init-resource hosts and append query parameters to playlists, including nested playlists. Browser code resolves relative URLs/URI="…" attributes, rewrites playlists into blob: URLs, drops failed variants, and bounds recursion, size and time. Those blob URLs are browser-local, not reusable native source URLs.
  • The browser loads strm.cx/client.js, renders <strmcx-embed>, and calls setSession with local player state/source/subtitle/skip data over strmcx/v1 messaging. This was not required for the tested source or direct-resource GETs. It is not evidence of an authenticated API session prerequisite.
  • strm client contains a best-effort POST /v1/embeds; it was source-inspected only, not invoked. Iframe providers and unrelated comment/telemetry endpoints were not exercised.

For a future native player: preserve fresh query tokens, per-server headers and relevant provider config; correctly resolve nested/relative HLS URLs. Do not apply one provider's headers indiscriminately to another provider's subtitles/thumbnails. Terebi currently receives only --new-instance --url … --progress-file PATH in Tubular (existing usage); custom headers, external subtitles, HLS overrides, native seeking and full Miruro playback remain unverified.

SvelteKit route data

Sources: deployed fetch/devalue decoder, route map, and live detail/watch/home responses.

Append /__data.json to the page path and retain query parameters. The browser transport can add x-sveltekit-invalidated=<bit string> and x-sveltekit-trailing-slash=1; neither was necessary in the research requests.

An illustrative, minimal envelope:

{"type":"data","nodes":[{"type":"data","data":[{"someKey":1},"someValue"],"uses":{}},null]}

Actual responses had layout at node 0, route at node 1. Each node's data is its own devalue flattened table, with root at index 0. Object-property and ordinary-array numeric values are references into that table, not literal payload numbers. Null is a table value; undefined uses sentinel -1. Decoder also handles array holes (-2), NaN/infinities/negative zero (-3…-6), sparse arrays (-7) and tagged Date/Set/Map/BigInt/other types. A generic recursive integer dereferencer is not a complete decoder. Keep layout configuration separate from route models.

Promised fields are tagged ['Promise', <table index referencing promise ID>]. Streamed text/sveltekit-data is newline-delimited JSON, not one JSON object:

{"type":"data","nodes":[...]}
{"type":"chunk","id":<promise ID>,"data":<independent devalue table>}

Resolve by decoded promise ID; each chunk has a new table. Arrival order is not field order: sample detail chunks arrived 2,1,6,3,4,5. IDs are response-local, not constants. Consume the full body or track promises incrementally. Frontend also decodes error chunks to reject a promise; that error path was not live-exercised. Top-level redirect envelopes require separate handling.

Verified: /info/invalid/invalid/__data.json returned HTTP 200 with {"type":"redirect","location":"/"}. Status 200 therefore does not prove a successful detail response. Empty search arrays, redirect envelopes, API problem responses and transport failures must remain distinct.

Errors and caching

Verified API problem shape:

{"type":"about:blank","title":"Bad Request","status":400,"detail":"Unsupported request parameters."}

Content-Type: application/problem+json, Cache-Control: no-store.

  • /api/sources without parameters: 400, detail:"Anime and episode are required.".
  • Known ID with n=0 or n=not-a-number: 400, detail:"Unsupported request parameters.".
  • id=invalid&n=1: same 400/detail; this does not establish behavior for a syntactically valid nonexistent ID.
  • Unsupported tested browse requests: 400, detail:"Unsupported browse request.".
  • Local curl of known ID with n=0: Cloudflare 403 HTML, before the JSON validation behavior seen in research transport. The cause/client fingerprint/IP distinction is unknown.

Source success and source errors had no Access-Control-Allow-Origin header. The direct Sun master/subtitle responses had ACAO https://ultracloud.cc. Neither observation guarantees general browser cross-origin access. Server rate limits, stable error taxonomy, authentication guarantees, token expiry and protection behavior are unknown; no rate/protection stress tests occurred.

Observed Cache-Control values:

Surface Cache-Control
Plain browse success public, max-age=300, s-maxage=900, stale-while-revalidate=3600, stale-if-error=86400
Plain schedule public, max-age=0, s-maxage=60, stale-while-revalidate=60
Discover references public, max-age=0, s-maxage=300, stale-while-revalidate=60
Source success/errors and normal route data no-store
Missing-detail redirect; discover feed private, no-store

Respect current response headers; these are sampled deployment values, not constants. Metadata cacheability does not make stream URLs durable.

Verification ledger

All research data GETs below used fetch with 20–25-second timeouts, no supplied auth/cookie/Origin/Referer unless stated. JS bundles used 15–20-second bounds. These are research-transport results, not successful local curl/native playback claims. Stream tokens and pagination cursors are intentionally not recorded.

Request Status / Content-Type Observed result
Autocomplete 200 / application/json Eight summaries; next_cursor; has_more
Browse without q, changed limit 2 400 / application/problem+json Unsupported browse request
Valid browse + own returned cursor as cursor 400 / application/problem+json Unsupported browse request
Full query search 200 / application/json Eleven summaries; no nextCursor
Full browse + its nextCursor request Both 200 / application/json Two 24-item pages; different first IDs
Unmatched search 200 / application/json Empty items; error:null
Detail 200 / text/sveltekit-data Initial detail + six promised chunks; two episodes
Invalid detail 200 / application/json Redirect envelope to /
Watch ep=1 200 / text/sveltekit-data Initial media + episodesData/sourcesData chunks
Source ep=1 200 / application/json ssub/sub, direct + iframe providers, runtime config
Source missing parameters / n=0 / nonnumeric n / invalid ID Each 400 / application/problem+json Validation details above
Home 200 / text/sveltekit-data Hero + rail/side-list chunks
Trending 200 / application/json 97 sampled items; year/season choices
Schedule page / plain schedule Both 200 / application/json 240 sampled entries
References 200 / application/json 19 genres, 1,742 tags in sample
Discover 200 / application/json Lens, 12 spotlights, 12 picks
Unseeded feed 200 / application/json Empty spotlights/picks
Fresh ssub/Sun first-server master, with server Origin/Referer 200 / application/vnd.apple.mpegurl #EXTM3U; one 1920x1080 variant; no redirect
Same provider default subtitle / thumbnail VTT, same headers Both 200 / text/vtt WEBVTT cues; thumbnail sprite coordinates; no redirect
strm client 200 / text/javascript Source inspected, not executed
Local shell curl, known source ID with n=0 403 / text/html Cloudflare block page

One worker's homepage fetch without a trailing slash returned nginx 404, while the trailing-slash HTML read succeeded. This unresolved transport/URL discrepancy is not evidence of an API rule. No browser testing, playback, media-segment downloads, account mutations, protection bypass, or application tests were performed for this documentation-only change.

Bounded reproduction requests

These curl recipes show request syntax; successful execution with local curl is not established. One local curl probe was blocked. Check status/Content-Type and stop on a block page. Example IDs/dates describe the observed catalog snapshot; titles and availability can change.

BASE='https://barelystarted.miruro.tv'
ID='OERe_a0YS4h-DlsBDOeXwX7HOO6316tw'
SLUG='the-apothecary-diaries-season-3'

curl --silent --show-error --max-time 25 -i --get "$BASE/api/search/browse" \
  --data-urlencode 'q=apothecary' --data-urlencode 'limit=8' --data-urlencode 'type=ANIME'

curl --silent --show-error --max-time 25 -i --get "$BASE/search/__data.json" \
  --data-urlencode 'q=apothecary'

curl --silent --show-error --max-time 25 -i "$BASE/info/$ID/$SLUG/__data.json"

curl --silent --show-error --max-time 25 -i --get "$BASE/watch/$ID/$SLUG/__data.json" \
  --data-urlencode 'ep=1'

curl --silent --show-error --max-time 25 -i --get "$BASE/api/sources" \
  --data-urlencode "id=$ID" --data-urlencode 'n=1'

For full-search pagination, decode nextCursor from /search/__data.json, then request the same route with --data-urlencode "cursor=$CURSOR", retaining filters/cookie context. For current schedule, decode weekStart from /schedule/__data.json, then request /api/schedule with startAt=<that value>&track=all. To inspect media resources, select fresh stream/subtitle URLs from the source response and use that server's actual headers, a bounded GET and in-memory results; do not save tokenized URLs to these notes.

Equivalent plain-JSON research request (runtime/network portability is unproven):

const response = await fetch(
  'https://barelystarted.miruro.tv/api/search/browse?q=apothecary&limit=8&type=ANIME',
  { signal: AbortSignal.timeout(20_000) }
);
console.log(response.status, response.headers.get('content-type'));
console.log(await response.text());

Open questions before implementation

  • Can the real native HTTP client access this deployment without being blocked? Local curl failed; no workaround has been implemented or tested.
  • Is there a stable plain metadata/episode API, or must the wrapper own a SvelteKit/devalue decoder tied to this deployment?
  • What are server-side filter/enum constraints, multi-value semantics, cursor lifetime, page-size-cookie limits, numeric-ID lookups and slug behavior?
  • What happens for unavailable episodes, empty tracks, provider failures, bad cursors, rejected promise chunks and syntactically valid missing IDs?
  • Which headers/config transforms are required per provider, and can Terebi propagate them to HLS playlists/segments/keys and external subtitles?
  • Are direct streams usable end-to-end, including rendition/segment access and seeking? Only master/subtitle/thumbnail retrieval was verified.
  • What are seeded discovery authentication requirements, rate limits, token expiry/IP binding and content-use permissions? Reachable internal endpoints do not imply a supported third-party integration or permission to redistribute content.

Hashed JavaScript URLs below identify the inspected deployment, not stable versions. For later research, re-read the homepage's entry-script imports and route map to locate the current bundles; do not hardcode these hashes in the wrapper.