API Documentation
Embed movies, TV shows, and anime on your own site through AniKino - one iframe, one URL, pulling from our MVP, KaF, Neko, RU MV, and Kino sources under the hood so you don't have to juggle providers yourself.
postMessage events,
startAt/resume-playback, and autoplay/autonext
(see their own sections further down). Nothing left marked TBD.
Introduction
Drop a single <iframe> into your own site and get a fully
working AniKino player back - no API key, no sign-up. Every embed resolves the
real stream server-side and serves it through AniKino's own Plyr-based player,
not a nested third-party iframe - the actual provider CDN URL is never exposed
to your page or your visitors.
<iframe
src="https://your-domain.example/embed/movie/{id}"
width="100%" height="560"
frameborder="0" allowfullscreen
></iframe>
{id} below accepts either a numeric TMDB id
or an IMDB id (tt1300854-style) - both are
resolved automatically, no need to tell us which one you're sending.
/embed/* is rate-limited to 40 requests / 5 minutes
per IP - plenty for a real viewer loading/reloading a player, not
enough for bulk scraping. It's also the only part of AniKino's API that's
deliberately reachable cross-origin; everything else is locked to our own
frontend.
Providers
Each embed type has its own default source and an alternate you can force with
?server=. Anime and Movie/TV don't share providers - the same
server=ru value means a different backend depending on which embed
you're calling, both labeled RU MV on the frontend since
they're both "the Russian-dub source" for their respective content type.
The dot on each card below is that provider's live status - checked server-side on a timer against a fixed, known-good title (not on page load), so it reflects whether the provider itself is actually reachable right now, not just this browser's own network.
MVP anime, default
Broad anime coverage, usually the fastest to resolve.
server=megaKaF anime
Broad anime coverage, but often the slowest of the four to resolve - and doesn't always have a dub (or the title at all).
server=kaaNeko anime
Lightweight, quick-resolving anime source - falls back to MVP automatically on titles it doesn't have.
server=nekoRU MV anime
Russian-dub anime source. Only serves a title when we're confident in the match - fails clearly rather than guessing wrong.
server=ruKino movie/TV, default
Our primary movie/TV source - broad English-language coverage.
server=kinoT1M movie/TV
English movie/TV source with its own large multi-language subtitle set - good alternate when Kino doesn't have a title.
server=t1mVixSrc movie/TV
English-preferred HLS movie/TV source with a plain-HTTP resolve - extra alternate on the player page (not available in /embed).
player onlyRU MV movie/TV
Russian-dub movie/TV source - single audio track, no subtitles of its own.
server=ruMovie Embed
Embed a movie player. Resolves through Kino (default), T1M, or RU MV.
| Parameter | Required | Description |
|---|---|---|
{id} | Yes | TMDB movie id or IMDB id (tt...) |
server | Optional | kino (default), t1m, or ru - see Providers |
startAt | Optional | Seconds into the video to start playback at - see Resume Playback |
autoplay | Optional | 1 to start playback automatically once the stream loads |
<iframe src="https://your-domain.example/embed/movie/{id}" width="100%" height="560" allowfullscreen></iframe>
TV Show Embed
Embed a specific episode. Resolves through Kino (default), T1M, or RU MV.
| Parameter | Required | Description |
|---|---|---|
{id} | Yes | TMDB TV id or IMDB id (tt...) |
{season} | Yes | Season number |
{episode} | Yes | Episode number within that season |
server | Optional | kino (default), t1m, or ru - see Providers |
startAt | Optional | Seconds into the video to start playback at - see Resume Playback |
autoplay | Optional | 1 to start playback automatically once the stream loads |
autonext | Optional | 1 to load episode + 1 (same season) automatically when this one ends |
<iframe src="https://your-domain.example/embed/tv/{id}/{season}/{episode}" width="100%" height="560" allowfullscreen></iframe>
Anime Embed
Embed an anime episode with sub/dub selection, resolved through MVP (default), KaF, Neko, or RU MV.
| Parameter | Required | Description |
|---|---|---|
{id} | Yes | TMDB TV id, IMDB id (tt...), MAL id (mal-...), or AniList id (anilist-... / al-...) - see the callout below. Anime is always the TMDB TV namespace, even for movies |
{episode} | Yes | Episode number within season |
season | Optional | Defaults to 1 |
audio | Optional | sub (default) or dub |
server | Optional | mega (default), kaa, neko, or ru - see Providers |
startAt | Optional | Seconds into the video to start playback at - see Resume Playback |
autoplay | Optional | 1 to start playback automatically once the stream loads |
autonext | Optional | 1 to load episode + 1 (same season/audio/server) automatically when this one ends |
<iframe src="https://your-domain.example/embed/anime/{id}/{episode}?season=1&audio=dub" width="100%" height="560" allowfullscreen></iframe>
{id}
always means TMDB (unchanged, existing behavior). To pass
a MAL or AniList id instead, prefix it: mal-269 or
anilist-269 (al-269 also works, same thing).
TMDB/MAL/AniList ids are all bare integers with overlapping ranges, so the
prefix is what disambiguates them - an unprefixed number is never treated
as a MAL/AniList id, even if the numbers happen to collide. This only
applies to the anime endpoint; Movie/TV Show Embed stay TMDB/IMDB-only
since MAL and AniList don't catalog live-action titles. First use of a
given MAL/AniList id costs one extra lookup (cached in our own DB after
that) to translate it to the TMDB id we actually resolve playback from.
server you pick, since MVP/Neko/RU MV
don't carry a useful subtitle track of their own. Kino (movie/TV) instead
pulls real OpenSubtitles-backed captions directly.
Query Parameters
Parameters supported today, plus what's still planned.
| Parameter | Type | Status | Description |
|---|---|---|---|
server | String | Live | Force a specific provider - see Providers for values per endpoint |
season | Integer | Live | Anime embed only - defaults to 1 |
audio | sub / dub | Live | Anime embed only - defaults to sub |
startAt | Float | Live | Seconds into the video to start playback at - see Resume Playback |
autoplay | 0 / 1 | Live | Starts playback automatically once the stream loads - muted-retry fallback if the browser blocks unmuted autoplay |
autonext | 0 / 1 | Live | TV/anime only - loads episode + 1 (same season, same server/audio) when the current one ends. Doesn't roll over to the next season |
Episode Release Progress
How many episodes of a season we've actually got cached (sub/dub/RU-dub counts), whether the season is finished, and an upcoming air date when we have one. Read-only, rate limited to 40 requests/minute per IP (separate from every other limiter on this page).
| Parameter | Required | Description |
|---|---|---|
tmdbId | One of tmdbId/title | TMDB TV id - the reliable lookup, checked first if both are given |
title | One of tmdbId/title | Only used when tmdbId isn't given - matched against a normalized title, so casing/punctuation don't need to match exactly |
season | Optional | Only applies with tmdbId - defaults to 1 |
fetch('https://your-domain.example/api/public/episode-progress?tmdbId=30984&season=4')
.then(function (r) { return r.json(); })
.then(function (data) { console.log(data); });
{
"ok": true,
"found": true,
"data": {
"tmdbId": 30984,
"season": 4,
"title": "Bleach",
"subEpisodes": 28,
"dubEpisodes": 28,
"totalEpisodes": 28,
"ruDubEpisodes": 28,
"isFinished": true,
"tmdbSeasonEpisodeCount": 28,
"upcomingDate": null,
"source": "anikoto+native",
"cachedAt": 1767225600000
}
}
found: false (with an ok: true, 200) means
we don't have this title/season cached yet - not an error, just nothing to
report. A genuinely malformed request (neither tmdbId nor
title given) returns 400.
Anime TMDB Mapping
Every other anime embed API out there is built around MAL or AniList ids - we're the outlier keying everything off TMDB (see the callout on Anime Embed). This translates between them: hand it a TMDB id we've already mapped and get back its MAL/AniList id, or the reverse via title. Same 40 requests/minute per IP limit as Episode Release Progress, read-only.
| Parameter | Required | Description |
|---|---|---|
tmdbId | One of tmdbId/title | TMDB TV id - the reliable lookup key, exact match (a tmdbId can span multiple AniList seasons; this returns the best match) |
title | One of tmdbId/title | Only used when tmdbId isn't given - case-insensitive match against whatever title string got cached, not a fuzzy/normalized match |
fetch('https://your-domain.example/api/public/tmdb-mapping?tmdbId=30984')
.then(function (r) { return r.json(); })
.then(function (data) { console.log(data); });
{
"ok": true,
"found": true,
"data": {
"tmdbId": 30984,
"malId": 269,
"anilistId": 269,
"title": "Bleach",
"cachedAt": 1767225600000,
"validatedAt": 1767225600000
}
}
We only have a mapping cached for a title once something on the site
has actually resolved it before - this isn't a full TMDB↔MAL/AniList
directory, just our own cache. found: false means we don't have
it yet.
Security
The raw upstream stream URL is never exposed anywhere - not in the page source,
not in a JSON response, not at any layer of a nested playlist. Every embed
resolves the real stream server-side and hands the player a
session-bound, encrypted, expiring proxy URL instead - only /api/m3u8-proxy
ever talks to the real CDN, and only for the exact browser session that resolved
it. Copying a resolved stream URL out of your browser's network tab and loading
it in a different browser/session returns 403 This link does not belong
to your session - verified live.
Player Events
The embedded player posts postMessage events to the parent window
at every play, pause, seek-ready, and end, plus a throttled position update
(at most once every 5 seconds) while playing - so your page can track playback
state without ever reaching into the iframe (which same-origin policy blocks
anyway).
window.addEventListener('message', function (e) {
if (!e.data || e.data.source !== 'anikino-embed') return;
// e.data.event: 'ready' | 'play' | 'pause' | 'timeupdate' | 'ended' | 'autonext'
// e.data.currentTime / e.data.duration: seconds (floats)
// 'autonext' also carries e.data.nextEpisodeUrl - fired right before the iframe
// navigates itself there (only when ?autonext=1 was set - see Query Parameters)
console.log(e.data.event, e.data.currentTime, e.data.duration);
});
* since the embed genuinely doesn't
know what domain is hosting it - same tradeoff YouTube/Vimeo embeds make.
The payload only ever carries playback position, nothing sensitive.
Resume Playback
Save and restore a viewer's watch position using the timeupdate
event above and the startAt query parameter - same idea as
VidSrc's resume flow. Listen for timeupdate, store
currentTime against that title/episode in your own backend or
localStorage, then next time build the iframe src
with ?startAt=<seconds> and the player seeks there as soon
as its metadata loads - no extra round trip needed.
var lastKnownPosition = 0;
window.addEventListener('message', function (e) {
if (!e.data || e.data.source !== 'anikino-embed') return;
if (e.data.event === 'timeupdate' || e.data.event === 'pause') {
lastKnownPosition = e.data.currentTime;
localStorage.setItem('resume:293660', lastKnownPosition);
}
});
// next visit - resume where they left off
var resumeAt = localStorage.getItem('resume:293660') || 0;
iframe.src = '/embed/movie/293660?startAt=' + resumeAt;
localStorage as the viewer
watches and resumes from there on the next load of that same title/episode,
scoped to our origin so it follows the viewer across every site that embeds
us. An explicit ?startAt= from your own page (like the example
above) always takes priority over it, so implementing your own resume flow
never conflicts with this - it's just a fallback for when you don't.
Captions Settings
Every player - /embed/* and the main site's own player alike -
has a Captions Settings entry under the gear icon's
Settings menu, right below Captions. It's
there because different providers ship separately-timed rips of the same
episode, so a caption borrowed from a different provider than the one actually
serving the video can drift out of sync by a few real seconds - not something
fixable at the source, so the viewer gets a manual nudge instead.
| Control | Options | Default |
|---|---|---|
| Size | S / M / L / XL / XXL / XXXL | M |
| Color | White / Black / Yellow / Red | White |
| Bold | Off / On | Off |
| Background | Off / On | On (a dark box behind the text keeps it readable over busy scenes) |
| Sync Offset | -1s / +1s, cumulative | +0s |
Saved to the viewer's own localStorage, per device - not sent to
us, and not something a host page can read or set through the embed API.
Sync Offset works by directly shifting the loaded caption track's cue timing
(not the video), so it applies instantly with no reload, and persists across
episode/server switches until changed again.
Test Your Embed
Questions & Support
Got a question about the embed API, found a bug, or want a title added or fixed? Send it here.
Last Updated
This page and the embed API were last updated August 26, 2026.