> ## Documentation Index
> Fetch the complete documentation index at: https://api.simkl.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Standard media objects

> The shape of Movie, Show, Anime, and Episode objects every endpoint accepts and returns.

Every endpoint that talks about a movie, show, anime, or episode uses the same JSON shapes. Learn them once and you can read or write any endpoint.

## The `ids` object

The `ids` object is the heart of every media object. It's how Simkl identifies what you mean across all the catalogs you can integrate with. Pass as many IDs as you have — Simkl resolves to the canonical record and returns the rest.

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{
  "ids": {
    "simkl": 53536,
    "imdb":  "tt0181852",
    "tmdb":  "296"
  }
}
```

Note the quoting: `simkl` is an integer, every other ID is a string. That's exactly how responses come back, and requests accept this form too — see [Supported ID keys](#supported-id-keys) below.

### Supported ID keys

<Warning>
  **Read either `ids.simkl` or `ids.simkl_id` — same integer, two key names.** Depending on the endpoint, Simkl's canonical catalog ID comes back as `ids.simkl` or `ids.simkl_id`. Robust reader code accepts both:

  ```js theme={"theme":{"light":"github-light","dark":"vesper"}}
  const id = item.ids.simkl ?? item.ids.simkl_id;
  ```

  The integer is **globally unique and permanent** — use it as your primary key for caching and cross-referencing.

  **`slug` is NOT unique** — multiple titles can share one (e.g. three different *Superman* movies live at `/movies/2059585/superman`, `/movies/260740/superman`, `/movies/951252/superman`). The slug is a URL hint only; never identify a record by slug alone.

  **External IDs in `ids` (`imdb`, `tmdb`, `tvdb`, `mal`, `anidb`, …) are echo-only.** To resolve one to a Simkl record, use [`/redirect`](/api-reference/simkl/redirect) — it returns the canonical Simkl URL with the `simkl` ID baked in.

  **`tmdb` is the one external ID that isn't globally unique** — TMDB keeps separate sequences for `movie` and `tv`, so the same numeric `tmdb` value can refer to either. When resolving a TMDB ID via [`/redirect`](/api-reference/simkl/redirect), always pass `type=movie` or `type=tv` alongside `tmdb`. When reading TMDB IDs back from responses, treat the container key (`movies` / `shows` / `anime`) as part of the ID's identity.
</Warning>

<Warning>
  **In responses, every ID is a string except `simkl` / `simkl_id`.** `ids.simkl` (and `ids.simkl_id`) come back as JSON **integers**; every other ID — including numeric-looking ones like `tmdb`, `tvdb`, `mal`, `anidb`, `anilist`, and `kitsu` — comes back as a **quoted string**:

  ```json theme={"theme":{"light":"github-light","dark":"vesper"}}
  "ids": {
    "simkl": 1321382,
    "slug":  "dune-part-two",
    "tmdb":  "693134",
    "imdb":  "tt15239678",
    "tvdb":  "290272"
  }
  ```

  Type your decoder accordingly — a struct that declares `tmdb` as `Int` will fail to decode. **On requests, both forms are accepted** (`"tmdb": 693134` and `"tmdb": "693134"` both resolve), so you only need to care about this when reading.
</Warning>

The table below gives the **response** type for each key.

| Key | Type in responses | Example | Notes |
| - | - | - | - |
| `simkl` / `simkl_id` | **integer** | `1321382` | Simkl's canonical ID. **The only integer in `ids`.** Globally unique and permanent. |
| `slug` | string | `"dune-part-two"` | URL hint only — **not unique**, never identify a record by it. |
| `imdb` | string | `"tt15239678"` | |
| `tmdb` | string | `"693134"` | **Not unique across types** — pair with `type=movie` or `type=tv` when resolving via `/redirect`. TMDB has no anime type; anime is filed under `tv`. |
| `tvdb` | string | `"290272"` | |
| `tvdbslug` | string | `"dune-part-two"` | |
| `traktslug` | string | `"dune-part-two-2024"` | |
| `letterboxd` | string | `"dune-part-two"` | Movies only. |
| `mal` | string | `"16498"` | Anime. |
| `anidb` | string | `"9541"` | Anime. |
| `anilist` | string | `"16498"` | Anime. |
| `kitsu` | string | `"7442"` | Anime. |
| `mdlslug` | string | `"808074-howl-s-moving-castle"` | MyDramaList; appears on some anime / drama titles. |

**Accepted on requests but not returned in responses.** These resolve a title when you send them to [`/redirect`](/api-reference/simkl/redirect) or [`/search/id`](/api-reference/simkl/search-by-id), but Simkl doesn't echo them back in `ids`: `crunchyroll`, `netflix`, `hulu`, `anisearch`, `animeplanet`, `livechart`, `anfo`, `ann`, `tvcom`, `zap2it`, `mdl`, `boxd`.

<Warning>
  **The CDN data files use different key names than the API.** Everything above describes `api.simkl.com`. The pre-built JSON files on `data.simkl.in` — [trending](/api-reference/trending), [DVD releases](/api-reference/trending), and [calendar](/api-reference/calendar) — are produced by a separate pipeline that still emits the older movie/series-qualified names:

  | API (`api.simkl.com`) | CDN files (`data.simkl.in`) |
  | - | - |
  | `letterboxd` | `letterslug` |
  | `traktslug` | `traktmslug` on movies, `trakttvslug` on TV / anime |
  | *(not returned)* | `jwtv` — JustWatch, on TV / anime |

  `simkl_id`, `slug`, `imdb`, `tmdb`, `tvdb`, `tvdbslug`, `mdlslug`, `mal`, `anidb`, `anilist`, and `kitsu` are spelled the same in both. If you read from both surfaces, normalize the three keys above before merging — otherwise the same title looks like it has different IDs depending on where you fetched it.
</Warning>

<Tip>
  **Send every identifier you have — `title`, `year`, and the full `ids` object.**

  Simkl walks the `ids` object in priority order (`simkl` first when present, then external IDs like `imdb`, `tmdb`, `tvdb`, `mal`, `anidb`, …). If no ID resolves, it falls back to a `title` + `year` match, then to title-only as a last resort. Sending everything you know — title, year, plus every external ID your client has cached — maximizes the chance the right item gets credited. Extra fields are free; missing fields can cause a `404 id_err` or, worse, a silent mismatch.

  **You don't need to search before writing.** Endpoints like `/scrobble/*`, `/sync/history`, `/sync/add-to-list`, and `/sync/ratings` resolve IDs server-side — pass whatever you have directly, no `/search/*` round-trip required. Calling `/search/id` first to "resolve" a Simkl ID is wasted work that doubles your API quota for no gain.

  See [Supported ID keys](/conventions/standard-media-objects#supported-id-keys) for the full list.
</Tip>

## Movie

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{
  "title": "Terminator 3: Rise of the Machines",
  "year":  2003,
  "ids": {
    "simkl": 53536,
    "imdb":  "tt0181852",
    "tmdb":  "296"
  }
}
```

When `simkl` is known, the minimum is just:

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{ "ids": { "simkl": 53536 } }
```

## Show

A show object can include nested seasons and episodes for partial sync:

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{
  "title": "The Walking Dead",
  "year":  2010,
  "ids": {
    "simkl": 2090,
    "tvdb":  "153021",
    "imdb":  "tt1520211"
  },
  "seasons": [
    {
      "number": 1,
      "episodes": [
        { "number": 1 },
        { "number": 2 }
      ]
    }
  ]
}
```

## Anime

Same JSON shape as Show, plus `anime_type` and an optional flat `episodes` list. Simkl uses AniDB as its primary metadata source — episodes follow the **anime-native model** by default (each cour is its own title, episodes restart at 1). Simkl also accepts Western TVDB/TMDB-style `season` + `number` coordinates and cross-maps both to the same canonical episode.

<Card title="Handling anime — full guide" icon="dragon" href="/guides/anime" horizontal>
  Two integration paths (TMDB/TVDB-primary with `use_tvdb_anime_seasons`, or anime-native with AniDB/MAL/AniList/Kitsu IDs), the cross-mapping rules, write/read recipes, and edge cases like Attack on Titan and Solo Leveling.
</Card>

<Tip>
  **Anime can go under either `shows[]` / `show` or `anime[]` / `anime`.** Simkl resolves items by their `ids` first, then routes to the correct catalog — the key you pick is for clarity, not routing. Match the field to your data type when known; fall back to `shows` / `shows[]` when you only have TMDB / TVDB IDs.

  | Endpoint family | Accepted top-level keys |
  | - | - |
  | [`/scrobble/start`](/api-reference/simkl/scrobble-start), [`/pause`](/api-reference/simkl/scrobble-pause), [`/stop`](/api-reference/simkl/scrobble-stop), [`/checkin`](/api-reference/simkl/scrobble-checkin) | `movie`, `show`, `anime`, `episode` *(singular objects, one per request)* |
  | [`/sync/history`](/api-reference/simkl/add-to-history), [`/sync/history/remove`](/api-reference/simkl/remove-from-history), [`/sync/add-to-list`](/api-reference/simkl/add-to-list), [`/sync/ratings`](/api-reference/simkl/add-ratings), [`/sync/ratings/remove`](/api-reference/simkl/remove-ratings) | `movies[]`, `shows[]`, `anime[]`, `episodes[]` *(plural arrays, batched)* |
  | [`/sync/watched`](/api-reference/simkl/get-watched) | top-level array of items, no wrapping key |

  **Anime-only IDs work anywhere** — `anidb`, `mal`, `anilist`, `kitsu`, `anisearch`, `animeplanet`, `livechart` inside `ids` resolve correctly whether the wrapper is `shows[]` or `anime[]`. For example, either of these adds Attack on Titan:

  ```json POST /sync/history — either form works theme={"theme":{"light":"github-light","dark":"vesper"}}
  { "anime": [ { "ids": { "anidb": 9541 } } ] }

  { "shows": [ { "ids": { "anidb": 9541 } } ] }
  ```

  **Caveat: `not_found` always buckets under `shows`.** If a write fails to resolve, the response's `not_found.shows` array carries the failed entry regardless of whether you sent it under `shows[]` or `anime[]`. There is no `not_found.anime` bucket.
</Tip>

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{
  "title": "Attack on Titan",
  "year":  2013,
  "anime_type": "tv",
  "ids": {
    "simkl": 39687,
    "mal":   "16498",
    "tvdb":  "267440",
    "imdb":  "tt2560140",
    "anidb": "9541"
  },
  "episodes": [
    { "number": 1 },
    { "number": 2 }
  ]
}
```

### `anime_type`

Anime items include an `anime_type` field with one of these values:

* `tv`
* `special`
* `ova`
* `movie`
* `music video`
* `ona`

## Episode

Inside a `seasons[].episodes[]` array (or a flat `episodes` array), an episode is identified by `season` + `number`, **or** directly by `ids`. Optionally include `watched_at` to record when it was watched.

<Tip>
  **Prefer `season` + `number` over episode IDs whenever you can.** The numeric `S1E4` coordinate is stable forever — it never changes once an episode airs. External episode IDs (TVDB / AniDB) get re-issued when the source catalog merges duplicates, re-numbers a season, or replaces a record, and **a stale episode ID returns `404`**. The `season` + `number` pair is the cheapest, most robust identifier for scrobble / sync writes — only fall back to `episode.ids` when your integration genuinely doesn't know which season/number it's dealing with (e.g. Plex-style scrapers that resolve a file to a single episode ID without season context).
</Tip>

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{
  "watched_at": "2014-09-01T09:10:11Z",
  "season": 1,
  "number": 4
}
```

If you only have an episode ID (no season/number), pass it via `ids`. For episode-ID lookups in scrobble / sync writes, Simkl accepts **only `tvdb` and `anidb`** at the episode level. If both are sent, `tvdb` is tried first. Episode-level `imdb` and `tmdb` IDs do **not** exist on Simkl — those external services only assign IDs at the show/movie level.

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{
  "watched_at": "2014-09-01T09:10:11Z",
  "ids": {
    "tvdb":  4274616,
    "anidb": 142278
  }
}
```

<Note>
  **`tvdb` and `anidb` are request-only at the episode level.** Catalog responses (e.g. [`/tv/episodes/{id}`](/api-reference/simkl/get-tv-episodes), [`/anime/episodes/{id}`](/api-reference/simkl/get-anime-episodes)) return a single key in each episode's `ids` — `simkl_id`, an **integer**, Simkl's own episode ID:

  ```json theme={"theme":{"light":"github-light","dark":"vesper"}}
  "ids": { "simkl_id": 1195016 }
  ```

  They do not echo `tvdb`, `anidb`, `hulu`, or `crunchyroll` back. Always send `season` + `number` (preferred) or `tvdb` / `anidb` when identifying an episode in a request.
</Note>

## Tips for sending media objects

<AccordionGroup>
  <Accordion title="Pass as much identifying data as you can">
    `title`, `year`, and any IDs you have. Simkl uses all of it to disambiguate. If two movies share a title and year, IDs save the day.
  </Accordion>

  <Accordion title="When sending IDs, simkl wins">
    If you send `simkl: 53536` and `imdb: "tt9999999"` and they disagree, Simkl uses the `simkl` ID. Send only one source of truth when you can.
  </Accordion>

  <Accordion title="Anime episode numbering — read this once">
    Simkl uses the anime-native model (each cour is its own title, episodes restart at 1) — same as AniDB / MAL / AniList / Kitsu / AniSearch / Anime-Planet / LiveChart / ANN. Western TV catalogs (TVDB / TMDB / IMDB) use the franchise-with-seasons model instead. The Scrobble and Sync endpoints accept both schemes and Simkl maps automatically — the response includes both `season`/`number` (anime-native) and `tvdb_season`/`tvdb_number` (Western style) for reference. See [Anime episode numbering](/guides/scrobble#anime-episode-numbering) for the full mapping rules.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.