# 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](#sveltekit-route-data), even when Content-Type is `application/json`. | Purpose | GET path and parameters | Response | | --- | --- | --- | | Autocomplete | `/api/search/browse?q=&limit=8&type=ANIME` | Plain JSON `{data, next_cursor, has_more}` | | Full search / browse | `/search/__data.json?q=&cursor=` | Route data: `{items, entityItems, nextCursor, hasMore, filters, …}` | | Detail / episode metadata | `/info///__data.json` | Streamed route data: `detail`, promised `episodes` and related metadata | | Watch metadata + initial sources | `/watch///__data.json?ep=` | Streamed route data: `media`, promised `episodesData` and `sourcesData` | | Fresh episode sources | `/api/sources?id=&n=` | 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=&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][routes], [search/layout][layout], [detail][detail-client], [watch][watch-client], [home][home-client], [trending][trending-client], [schedule][schedule-client], [discover][discover-client], 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: ```text 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=&n=` 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][link-helpers] generate `/info//` and `/watch//?ep=`. 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][browse-live] 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: ```json { "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": "", "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: ```text { 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][search-client] 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][filter-options]), **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=&exclude=&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: ```text { 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: ```json { "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: ```text { episodes: [{id:"1",number:1,title,image,airDate,duration:1500,description,filler:false}, ...], currentEpisode: , prevEpisode: null, nextEpisode: , 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][sources-live] returned the following observed structure. Fields seen only as null have unknown non-null types; this is not an exhaustive guaranteed schema. ```text 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): ```json { "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":"","format":"hls","quality":null,"resolution":null,"codec":null,"embed":null}] }], "subtitles":[{"language":"en","label":"English","file":"","format":"vtt","default":true}], "thumbnails":{"vtt":"","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][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][homepage] 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][strm-client], renders ``, 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](../README.md#usage)); custom headers, external subtitles, HLS overrides, native seeking and full Miruro playback remain unverified. ## SvelteKit route data Sources: [deployed fetch/devalue decoder][transport-client], [route map][routes], 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=` and `x-sveltekit-trailing-slash=1`; neither was necessary in the research requests. An **illustrative**, minimal envelope: ```json {"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', ]`. Streamed `text/sveltekit-data` is **newline-delimited JSON**, not one JSON object: ```text {"type":"data","nodes":[...]} {"type":"chunk","id":,"data":} ``` 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:** ```json {"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][browse-live] | 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][search-live] | 200 / application/json | Eleven summaries; no nextCursor | | [Full browse][search-all-live] + its nextCursor request | Both 200 / application/json | Two 24-item pages; different first IDs | | [Unmatched search][empty-search-live] | 200 / application/json | Empty items; error:null | | [Detail][detail-live] | 200 / text/sveltekit-data | Initial detail + six promised chunks; two episodes | | [Invalid detail][missing-detail-live] | 200 / application/json | Redirect envelope to `/` | | [Watch ep=1][watch-live] | 200 / text/sveltekit-data | Initial media + episodesData/sourcesData chunks | | [Source ep=1][sources-live] | 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][home-live] | 200 / text/sveltekit-data | Hero + rail/side-list chunks | | [Trending][trending-live] | 200 / application/json | 97 sampled items; year/season choices | | [Schedule page][schedule-live] / [plain schedule][events-live] | Both 200 / application/json | 240 sampled entries | | [References][refs-live] | 200 / application/json | 19 genres, 1,742 tags in sample | | [Discover][discover-live] | 200 / application/json | Lens, 12 spotlights, 12 picks | | [Unseeded feed][feed-live] | 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][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. ```sh 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=&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): ```js 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][routes] to locate the current bundles; do not hardcode these hashes in the wrapper. [routes]: https://barelystarted.miruro.tv/_app/immutable/entry/app.BSfZufle.js [layout]: https://barelystarted.miruro.tv/_app/immutable/nodes/0.TgQ_FRp6.js [search-client]: https://barelystarted.miruro.tv/_app/immutable/nodes/5.B8psgBrv.js [filter-options]: https://barelystarted.miruro.tv/_app/immutable/chunks/BFtFCiqs.js [link-helpers]: https://barelystarted.miruro.tv/_app/immutable/chunks/DRUtYxUt.js [detail-client]: https://barelystarted.miruro.tv/_app/immutable/nodes/6.BPCeS58O.js [watch-client]: https://barelystarted.miruro.tv/_app/immutable/nodes/11.CVVa7Rmv.js [home-client]: https://barelystarted.miruro.tv/_app/immutable/nodes/2.D7a6D6w9.js [trending-client]: https://barelystarted.miruro.tv/_app/immutable/nodes/9.CWTtBRa5.js [schedule-client]: https://barelystarted.miruro.tv/_app/immutable/nodes/7.BJ4_XLlf.js [discover-client]: https://barelystarted.miruro.tv/_app/immutable/nodes/12.C2zcxgjP.js [transport-client]: https://barelystarted.miruro.tv/_app/immutable/chunks/DGYr79W3.js [homepage]: https://barelystarted.miruro.tv/ [strm-client]: https://strm.cx/client.js [browse-live]: https://barelystarted.miruro.tv/api/search/browse?q=apothecary&limit=8&type=ANIME [search-live]: https://barelystarted.miruro.tv/search/__data.json?q=apothecary [search-all-live]: https://barelystarted.miruro.tv/search/__data.json [empty-search-live]: https://barelystarted.miruro.tv/search/__data.json?q=zzzznoanimezzzz [detail-live]: https://barelystarted.miruro.tv/info/OERe_a0YS4h-DlsBDOeXwX7HOO6316tw/the-apothecary-diaries-season-3/__data.json [missing-detail-live]: https://barelystarted.miruro.tv/info/invalid/invalid/__data.json [watch-live]: https://barelystarted.miruro.tv/watch/OERe_a0YS4h-DlsBDOeXwX7HOO6316tw/the-apothecary-diaries-season-3/__data.json?ep=1 [sources-live]: https://barelystarted.miruro.tv/api/sources?id=OERe_a0YS4h-DlsBDOeXwX7HOO6316tw&n=1 [home-live]: https://barelystarted.miruro.tv/__data.json [trending-live]: https://barelystarted.miruro.tv/trending/__data.json [schedule-live]: https://barelystarted.miruro.tv/schedule/__data.json [events-live]: https://barelystarted.miruro.tv/api/schedule?startAt=1791763200&track=all [refs-live]: https://barelystarted.miruro.tv/api/discover/refs [discover-live]: https://barelystarted.miruro.tv/discover/__data.json [feed-live]: https://barelystarted.miruro.tv/discover/feed