Kitsu
Kitsu records, keyed by kitsu_id.
Kitsu keeps its own cross-service mapping table — mal_id,
anilist_id and anidb_id come straight from it — and it is the only
upstream here with a real per-episode dataset.
Kitsu maps no Anime News Network ids at all, so there is no ann_id on a
Kitsu record. For that side, go through /api/map/{mal_id}. Kitsu's edges are also what the map scores highest (99.3% agreement).
Routes
GET/api/kitsu/ids
Every kitsu_id animap has actually fetched, with its canonical title, ordered
by kitsu_id. Every id it returns resolves on /api/kitsu/{id}. An
empty dataset returns [], never null. title may be
null.
curl https://animap.id/api/kitsu/ids
[
{"kitsu_id": 1, "title": "Cowboy Bebop"},
{"kitsu_id": 2, "title": "Cowboy Bebop: Tengoku no Tobira"},
{"kitsu_id": 3, "title": "Trigun"}
]
GET/api/kitsu/{id}
The parsed record for one kitsu_id. 404 if that id has not been
fetched. Ratings, user counts and the global ranks are not in this
projection — they change constantly, so they are served only by
/api/kitsu/{id}/raw. url is the Kitsu entry for this id; please
keep it visible if you display the record. See the field reference below.
curl https://animap.id/api/kitsu/1
{
"kitsu_id": 1,
"slug": "cowboy-bebop",
"canonical_title": "Cowboy Bebop",
"title_en": "Cowboy Bebop",
"title_en_jp": "Cowboy Bebop",
"title_ja_jp": "カウボーイビバップ",
"other_titles": ["Cowboy Bebop"],
"abbreviated_titles": ["COWBOY BEBOP"],
"description": "In the year 2071, humanity has colonized…",
"subtype": "TV",
"status": "finished",
"tba": null,
"age_rating": "R",
"age_rating_guide": "17+ (Violence & Profanity)",
"nsfw": false,
"episode_count": 26,
"episode_length": 24,
"total_length": 624,
"start_date": "1998-04-03",
"end_date": "1999-04-24",
"poster_image": "https://media.kitsu.io/anime/poster_images/1/original.png",
"cover_image": "https://media.kitsu.io/anime/cover_images/1/original.png",
"youtube_video_id": "qig4KOK2R2g",
"url": "https://kitsu.io/anime/cowboy-bebop",
"mal_id": 1,
"anilist_id": 1,
"anidb_id": 23,
"absent": false,
"absent_at": null
}
GET/api/kitsu/{id}/raw
The full upstream Kitsu record for this id, served verbatim as stored:
the JSON:API resource object (id, type, attributes)
plus a resolved mappings array. Much richer than the parsed record: this is
where averageRating, ratingFrequencies, userCount,
favoritesCount, popularityRank and ratingRank live,
along with every posterImage / coverImage size variant, the
whole titles locale map, and nextRelease. 404 if
the id is unknown or its payload has not been fetched yet.
Kitsu's JSON:API links and relationships are not kept:
they are boilerplate derivable from the id, and relationships in particular
is only a pointer into a batch-scoped included block that does not survive
one record. The information in it — the cross-service mappings — is resolved at fetch time
and stored as the flat mappings array you see here. These counters are a
snapshot taken when we last fetched the record, not a live value.
curl https://animap.id/api/kitsu/1/raw
{
"id": "1",
"type": "anime",
"attributes": {
"slug": "cowboy-bebop",
"canonicalTitle": "Cowboy Bebop",
"titles": {"en": "Cowboy Bebop", "en_jp": "Cowboy Bebop", "ja_jp": "カウボーイビバップ"},
"subtype": "TV",
"status": "finished",
"averageRating": "82.29",
"ratingFrequencies": {"2": "13", "3": "1", "4": "6"},
"userCount": 138416,
"favoritesCount": 3958,
"popularityRank": 28,
"ratingRank": 27,
"posterImage": {"tiny": "…", "small": "…", "original": "…"}
},
"mappings": [
{"id": "1", "externalSite": "myanimelist/anime", "externalId": "1"},
{"id": "9578", "externalSite": "anilist/anime", "externalId": "1"},
{"id": "43151", "externalSite": "anidb", "externalId": "23"}
]
}
GET/api/kitsu/{id}/episodes
Every episode of one anime, in broadcast order (season_number, then
number). Kitsu is the only upstream here with a real per-episode dataset, so
this is the only place episode titles, air dates and synopses exist — the other four give
you an episode count and nothing more.
[] and 404 mean different things, and the difference
matters if you are mirroring.
An empty episodes array with 200 is a real answer:
this anime has no episodes on Kitsu (a movie, an unaired entry, a listing Kitsu
has not broken out). A 404 means
we have never fetched this anime's episode list — the id may be perfectly valid
on /api/kitsu/{id}. Do not record a 404 as "0 episodes".
Episodes are nested under the anime — there is no
/api/kitsu/episodes/{id}, and no
/api/kitsu/episodes/ids listing. An episode is only meaningful as part of a
series, we fetch a whole anime's episodes as one unit, and a flat listing of all ~292,000
of them would be a ~10 MB response nobody wants. Start from
/api/kitsu/ids and walk it.
curl https://animap.id/api/kitsu/1/episodes
{
"kitsu_id": 1,
"url": "https://kitsu.io/anime/cowboy-bebop",
"episodes": [
{
"kitsu_episode_id": 1,
"kitsu_id": 1,
"canonical_title": "Asteroid Blues",
"title_en_us": "Asteroid Blues",
"title_en_jp": "Asteroid Blues",
"title_ja_jp": "アステロイドブルース",
"other_titles": [],
"description": "Spike and Jet pursue a bounty on Tijuana…",
"season_number": 1,
"number": 1,
"relative_number": 1,
"airdate": "1998-04-03",
"length_minutes": 25,
"thumbnail": "https://media.kitsu.io/episodes/thumbnails/1/original.jpg"
}
]
}
GET/api/kitsu/{id}/episodes/{episode_id}
One episode, exactly as it appears in the list above, plus the anime's url.
It is scoped to its anime: an episode_id that belongs to a
different anime is a 404 under this path, even though it exists. Both ids
must be positive integers, or it is a 400.
curl https://animap.id/api/kitsu/1/episodes/1
{
"kitsu_episode_id": 1,
"kitsu_id": 1,
"canonical_title": "Asteroid Blues",
"title_en_us": "Asteroid Blues",
"title_en_jp": "Asteroid Blues",
"title_ja_jp": "アステロイドブルース",
"other_titles": [],
"description": "Spike and Jet pursue a bounty on Tijuana…",
"season_number": 1,
"number": 1,
"relative_number": 1,
"airdate": "1998-04-03",
"length_minutes": 25,
"thumbnail": "https://media.kitsu.io/episodes/thumbnails/1/original.jpg",
"url": "https://kitsu.io/anime/cowboy-bebop"
}
GET/api/kitsu/{id}/episodes/raw
The whole episode list for this anime as we stored it from Kitsu,
verbatim: each entry is the upstream episode object flattened to
{id, …attributes}, ordered by episode id. This is where the fields the parsed
projection leaves out live — synopsis (a duplicate of
description in every episode we measured), createdAt,
updatedAt, and the thumbnail object rather than just its
original URL. Same 404 rule as above.
curl https://animap.id/api/kitsu/1/episodes/raw
{
"kitsu_id": 1,
"episodes": [
{
"id": "1",
"canonicalTitle": "Asteroid Blues",
"titles": {"en_us": "Asteroid Blues", "en_jp": "Asteroid Blues", "ja_jp": "アステロイドブルース"},
"seasonNumber": 1,
"number": 1,
"relativeNumber": 1,
"airdate": "1998-04-03",
"length": 25,
"synopsis": "Spike and Jet pursue a bounty on Tijuana…",
"description": "Spike and Jet pursue a bounty on Tijuana…",
"createdAt": "2013-02-20T16:00:14.183Z",
"updatedAt": "2023-11-01T09:12:22.918Z",
"thumbnail": {"original": "https://media.kitsu.io/episodes/thumbnails/1/original.jpg"}
}
]
}
Field reference — /api/kitsu/{id}
| Field | Type | Notes |
|---|---|---|
| kitsu_id | integer |
The Kitsu id. Stable primary key. Unrelated to anidb_id,
mal_id, anilist_id and ann_id.
|
| slug | string · null |
Kitsu's URL slug (cowboy-bebop). Not derivable from the id — it is
what builds url.
|
| canonical_title | string · null |
The title Kitsu itself displays. This is what /api/kitsu/ids returns
as title.
|
| title_en title_en_jp title_ja_jp |
string · null |
The English, romaji and native titles. Kitsu stores titles as an open-ended
locale map, not a fixed set of fields, so these three are the common
locales pulled out by name; title_en is null for about
half of entries.
|
| other_titles | array of string |
Every other locale's title (en_us, zh_cn,
ko_kr, …), so nothing in the locale map is lost. Empty
[] rather than null.
|
| abbreviated_titles | array of string |
Short forms and alternative spellings Kitsu lists. Empty [] rather
than null.
|
| description | string · null | Synopsis, as written on Kitsu. Plain text (unlike AniList's, which carries HTML). |
| subtype | string · null |
TV, movie, ONA, OVA,
special or music. Note the mixed casing — it is Kitsu's,
verbatim.
|
| status | string · null |
finished, current, upcoming,
unreleased or tba.
|
| tba | string · null |
Kitsu's free-text note about an unannounced date — "2027",
"tba", a whole sentence. A string, not a boolean,
despite the name. null for most entries.
|
| age_rating | string · null | G, PG, R or R18. |
| age_rating_guide | string · null |
Free-form gloss on the rating, e.g. 17+ (Violence & Profanity).
|
| nsfw | boolean | Kitsu's adult flag — the anidb.restricted analogue. |
| episode_count | integer · null |
null when unknown (often for currently-airing or unaired entries).
|
| episode_length total_length |
integer · null |
Minutes per episode, and minutes for the whole run — real numbers, unlike MAL's
free-form duration string and ANN's running_time.
|
| start_date end_date |
string · null |
Air-start / air-end date as YYYY-MM-DD. Kitsu emits a full date or
nothing, so there is no partial-date caveat here (contrast AniDB's
startyear and AniList's split date parts).
|
| poster_image cover_image |
string · null |
Full image URLs on media.kitsu.io, the
original size only. The tiny/small/medium/large variants are in
/raw. Fetch them from there, not through this API.
|
| youtube_video_id | string · null | Bare YouTube id of the trailer, not a URL — build youtube.com/watch?v=<id> yourself. |
| url | string |
The Kitsu entry for this id (kitsu.io/anime/<slug>, falling back to kitsu.io/anime/<kitsu_id> when
the slug is null). Always present. Keep it visible when you display
the record — see the note at the foot of this page.
|
| mal_id anilist_id anidb_id |
integer · null |
Kitsu's own cross-references, taken from its mappings — join them to
/api/mal/{id}, /api/anilist/{id} and
/api/anidb/{id}. Each is null when Kitsu records no mapping to
that service or more than one: an unambiguous id, or nothing. There is no
ann_id — Kitsu maps no Anime News Network ids at all. Kitsu's
mappings are community-editable, so treat them as a strong hint, not a guarantee.
|
| absent | boolean |
true when Kitsu no longer serves this id. The record is kept and
still served — that is when this mirror is most useful. The flag clears if the
entry reappears.
|
| absent_at | string · null |
RFC 3339 timestamp of when the entry was first observed missing.
null whenever absent is false.
|
No ratings, user counts or ranks here. averageRating,
ratingFrequencies, userCount, favoritesCount,
popularityRank and ratingRank are left out of the parsed
projection for the same reason as MAL's and AniList's counters — and the two ranks are
worse than the rest, being global positions that shift for thousands of titles
whenever one of them gains a vote. They are still archived, and
/api/kitsu/{id}/raw returns them verbatim — read them as a snapshot, not a
live figure.
Field reference — /api/kitsu/{id}/episodes
The fields of one episode. The same object appears as an element of
/api/kitsu/{id}/episodes and as the whole body of
/api/kitsu/{id}/episodes/{episode_id} (which adds url, the anime's
Kitsu entry — the list carries it once, at the top level, rather than on every episode).
| Field | Type | Notes |
|---|---|---|
| kitsu_episode_id | integer |
The episode's own Kitsu id — a different id space from
kitsu_id. Globally unique across all anime, so it is the key, but it
only resolves under its own anime's path.
|
| kitsu_id | integer |
The anime this episode belongs to — the id in the URL, repeated on every element
so a single episode lifted out of the array is still self-describing. Join it to
/api/kitsu/{id}.
|
| canonical_title | string · null |
The title Kitsu itself displays for the episode. Often null for
never-localised entries, and frequently just "Episode 12".
|
| title_en_us title_en_jp title_ja_jp |
string · null |
Note the en_us. Episodes key their locale map
differently from the anime resource: an anime has en /
en_jp / ja_jp (hence title_en on
/api/kitsu/{id}), an episode has en_us /
en_jp / ja_jp. That is Kitsu's, not ours — an episode
has no en title at all, so do not expect a
title_en here.
|
| other_titles | array of string |
Every other locale's title, so nothing in the locale map is lost. Empty
[] rather than null.
|
| description | string · null |
The episode synopsis. Kitsu also carries a synopsis field, but it was
identical to this one in every episode we compared, so only this one is projected
— both are in /api/kitsu/{id}/episodes/raw if you want to check.
|
| season_number | integer · null | Kitsu's own seasoning of the series, not a broadcast season. Most series are all season 1; a few use it to split long runs. |
| number relative_number |
integer · null |
The absolute episode number within the series, and the number within its season.
Both are nullable and neither is unique — they are
community-entered, and duplicates and gaps exist. Sort on them for display (that
is the order this route returns), but key on kitsu_episode_id.
|
| airdate | string · null |
First broadcast, YYYY-MM-DD. null for unaired episodes
and for a great many older ones nobody has filled in.
|
| length_minutes | integer · null |
Runtime in minutes (Kitsu's length). null when unknown.
|
| thumbnail | string · null | Full image URL on media.kitsu.io, the original size only. Fetch it from there, not through this API. |
| url | string |
Only on /api/kitsu/{id}/episodes/{episode_id}. The
Kitsu entry for the anime (Kitsu has no public per-episode page). On the
list route the same link sits once at the top level. Keep it visible when you
display the record.
|