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.