> ## 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.

# Look up watched status for items

> POST an array of items you already know about; Simkl returns a parallel array telling you, **per item**, whether it's in the user's library, its current status, last-watched timestamp, and (optionally) per-episode breakdown.

Use this **only** when you don't already cache the user's full library locally — typical case is a media-server plugin or a deep-link landing page that needs to check "is this title in the user's tracker yet?" for a handful of specific titles, without syncing the whole library first.

> ⚠️ **Don't use this endpoint if your app already pulls [`GET /sync/all-items/{type}/{status}`](/api-reference/simkl/get-all-items).** The full-library response already contains the same per-item watch state, statuses, and last-watched timestamps that `/sync/watched` returns — your local cache has the answer. Calling both is wasted requests, counts twice against your rate-limit quota, and is one of the patterns that gets an app's `client_id` suspended. The correct loop for tracker apps that sync the full library is the two-phase model: full pull once, then `/sync/activities`-gated incremental refresh — see the [Sync guide](/guides/sync).

#### Item identification

Each input item carries one or more IDs. Simkl resolves to the canonical record before looking up watch state, so any of these work:

| ID style | Example |
|---|---|
| Simkl ID | `{ "ids": { "simkl": 2090 } }` |
| IMDb / TMDB / TVDB / MAL / AniDB / AniList / Kitsu | `{ "ids": { "imdb": "tt1520211" } }` |
| Title + year fallback | `{ "title": "Inception", "year": 2010 }` |

Pair `season` + `episode` on an item to ask 'has the user watched this specific episode?' instead of 'is this title in the library?'.

#### Query params

| Param | Effect |
|---|---|
| `extended=episodes` | Include per-episode breakdown (`seasons[].episodes[]` arrays) for shows/anime. **Limit: 100 items per call** when this is set — sending more triggers `400 max_items`. |
| `extended=specials` | Include specials (season `0`). Only effective when combined with `episodes`. |
| `extended=counters` | When sent **alone** (without `episodes`), the `seasons[]` array is included but its `episodes[]` arrays are omitted — useful when you only want totals without the per-episode payload. When sent **together with `episodes`**, the per-episode arrays are still included. |

Multiple values are comma-separated: `extended=episodes,specials`.

#### Response shape (per item)

The response is an array of the same length as the request, in the same order. Each entry echoes the input identifiers and adds:

| Field | When present | Notes |
|---|---|---|
| `result` | always | `true` if the user has watched (or is watching) this item. `false` if Simkl matched the IDs but the item isn't in the user's library. `"not_found"` if Simkl couldn't match the IDs at all — in this case only `result` is returned, no `simkl`/`list`/etc. |
| `simkl` | when `result` ≠ `"not_found"` | Canonical Simkl ID. |
| `list` | when matched | Current watchlist status (`watching`, `completed`, `plantowatch`, `hold`, `dropped`) or `null` if not in any list. |
| `last_watched_at` | when matched | ISO-8601 timestamp of the most recent watch event, or `null` if never watched. |
| `episodes_total` / `episodes_aired` / `episodes_to_be_aired` / `episodes_watched` | with `extended=episodes` or `extended=counters` (shows/anime only) | Aggregate counts across all seasons. |
| `seasons[]` | with `extended=episodes` or `extended=counters` (shows/anime only) | Per-season `{number, episodes_total, episodes_aired, episodes_to_be_aired, episodes_watched}`. With `extended=episodes` alone, each season also includes an `episodes[]` array (per-episode `{number, watched, aired, last_watched_at}`). With `extended=counters` alone, `episodes[]` is omitted. |

**Empty body quirk.** Sending an empty array `[]` returns the literal `null` (not `[]`). Treat both as 'no items to check'.

#### Errors

| Status | `error` | When |
|---|---|---|
| 400 | `max_items` | More than 100 items in a single call when `extended=episodes` (or any other `extended` value that triggers per-episode loading) is set. |

<Card title="Sync guide — full walkthrough" icon="arrows-rotate" href="/guides/sync" horizontal>
 Two-phase model (initial pull → activities-checked delta loop), `date_from` semantics, when to use `/sync/watched` vs `/sync/all-items`, and reference implementations.
</Card>

<Tip>
**Which IDs can I send/expect?** All accepted input identifiers and the keys you'll see echoed back in responses are listed at [**Standard media objects → Supported ID keys**](/conventions/standard-media-objects#supported-id-keys). Send every ID you have on writes — Simkl picks the first that resolves and ignores the rest. Reminder: `slug` is **response-only** (never send it on a request).
</Tip>



## OpenAPI

````yaml /openapi.json post /sync/watched
openapi: 3.1.0
info:
  title: Simkl API
  version: 1.0.0
  description: >-
    The **Simkl API** lets you build apps that track Movies, TV Shows, and Anime
    — scrobble playback, sync watch history, and pull rich metadata.


    All requests use HTTPS and return JSON. The base URL is
    `https://api.simkl.com`.


    **Every request needs three URL parameters and a `User-Agent` header:**


    ```

    /endpoint?client_id=YOUR_CLIENT_ID&app-name=my-app&app-version=1.0

    ```


    See [Headers and required parameters](/conventions/headers) for the full
    reference. Endpoints that read or modify a user's data also need an
    `Authorization: Bearer <token>` header — see [OAuth
    2.0](/api-reference/oauth) or the [PIN flow](/api-reference/pin).


    > ⚠️ **About the auto-rendered cURL examples on each endpoint page:** they
    omit `?client_id=…&app-name=…&app-version=…` from the URL for brevity. **You
    must add them yourself when copy-pasting**, or use the interactive **"Try
    it"** playground (it fills them in for you). This is a Mintlify rendering
    convention — every Mintlify-built API doc behaves the same way.


    **Get started**


    - [Quickstart](/quickstart) — first request in 60 seconds

    - [API rules](/api-rules) — what's allowed, what isn't

    - [Standard media objects](/conventions/standard-media-objects) — the shapes
    every endpoint speaks

    - [Errors](/conventions/errors) — every status code Simkl returns
  contact:
    name: Simkl Developer Support
    url: https://support.simkl.com
  termsOfService: https://simkl.com/about/policies/terms/
  license:
    name: Simkl API Terms of Use
    url: https://simkl.com/about/policies/terms/
  x-logo:
    url: https://i.simkl.com/img_tv/apiary_logo_api.png
    backgroundColor: '#0E5DAB'
    altText: Simkl
servers:
  - url: https://api.simkl.com
    description: Production API
  - url: https://data.simkl.in
    description: CDN for static, public data files (Calendar + Trending). No auth required.
security:
  - clientId: []
  - simklApiKey: []
tags:
  - name: OAuth 2.0
    description: >-
      OAuth 2.0 authorization-code flow for web and mobile apps. The user is
      redirected to Simkl, signs in, approves your app, and is redirected back
      with a `code` you exchange for an `access_token`. Long-lived tokens — no
      refresh-token flow needed.
    externalDocs:
      description: OAuth 2.0 walkthrough
      url: https://api.simkl.org/api-reference/oauth
  - name: PIN
    description: >-
      Device flow for TVs, consoles, smart watches, CLI tools — anywhere typing
      a URL is hard. Show a 5-character code; user enters it on simkl.com/pin;
      you poll for the access token. **No `client_secret` required.**
    externalDocs:
      description: PIN flow walkthrough
      url: https://api.simkl.org/api-reference/pin
  - name: Redirect
    description: >-
      Helper redirects for one-click "mark as watched", trailers, sharing, and
      more.
  - name: Search
    description: Find shows, movies, and anime by ID, text query, file name, or randomly.
    externalDocs:
      description: Search guide
      url: https://api.simkl.org/guides/search
  - name: Movies
    description: >-
      Browse the Simkl movies catalog: details, premieres, top-of-best, and
      genre filters.
  - name: TV
    description: >-
      Browse the Simkl TV catalog: details, episodes, what's airing, premieres,
      top-of-best, and genre filters.
  - name: Anime
    description: >-
      Browse the Simkl anime catalog: details, episodes, what's airing,
      premieres, top-of-best, and genre filters.
  - name: Ratings
    description: >-
      Read Simkl's average community rating, rank, drop rate, and external
      ratings (IMDB, MAL) for any item, or for every item in a user's lists.
  - name: Scrobble
    description: >-
      Report real-time playback to Simkl with `start`, `pause`, and `stop`. At ≥
      80 % progress Simkl marks the item as watched automatically; below 80 %
      the session is saved as a paused playback the user can resume from any
      device.


      See [Standard media objects](/conventions/standard-media-objects) for the
      supported ID keys.


      <!-- IDS_TABLE_START -->


      ### Supported ID keys


      Inside any `ids` object, pass as many of these as you have. Simkl resolves
      to the canonical record.


      | ID key | Type | Example |

      |---|---|---|

      | `simkl` | integer | `49108`. Simkl's canonical ID. Most reliable. |

      | `imdb` | string | `tt1520211` |

      | `tmdb` | integer | `76757` (for TV, specify `type`) |

      | `tvdb` | int / string | `153021` or `the-walking-dead` |

      | `mal` | integer | `4246` (MyAnimeList) |

      | `anidb` | integer | `10846`. Specifying just this is enough for anime
      lookups. |

      | `anilist` | integer | `21` |

      | `kitsu` | integer | `12` |

      | `anisearch` | integer | `2227` |

      | `animeplanet` | string | `one-piece` |

      | `livechart` | integer | `321` |

      | `letterboxd` | string | `the-truman-show` |

      | `netflix` | integer | `70210890` (movie ID) |

      | `traktslug` | string | `john-wick-chapter-4-2023` |


      > 📖 Full reference: [Standard media
      objects](/conventions/standard-media-objects)


      <!-- IDS_TABLE_END -->
    externalDocs:
      description: Scrobble guide
      url: https://api.simkl.org/guides/scrobble
  - name: Sync
    description: >-
      Use Simkl as a cloud backup for the user's watch history and lists
      (Watching, Plan to Watch, On Hold, Dropped, Completed). **Always check
      [`/sync/activities`](/api-reference/simkl/get-activities) first** and sync
      only the lists that have moved.


      All write endpoints accept arrays — batch aggressively. See [Standard
      media objects](/conventions/standard-media-objects) for the supported ID
      keys.


      <!-- IDS_TABLE_START -->


      ### Supported ID keys


      Inside any `ids` object, pass as many of these as you have. Simkl resolves
      to the canonical record.


      | ID key | Type | Example |

      |---|---|---|

      | `simkl` | integer | `49108`. Simkl's canonical ID. Most reliable. |

      | `imdb` | string | `tt1520211` |

      | `tmdb` | integer | `76757` (for TV, specify `type`) |

      | `tvdb` | int / string | `153021` or `the-walking-dead` |

      | `mal` | integer | `4246` (MyAnimeList) |

      | `anidb` | integer | `10846`. Specifying just this is enough for anime
      lookups. |

      | `anilist` | integer | `21` |

      | `kitsu` | integer | `12` |

      | `anisearch` | integer | `2227` |

      | `animeplanet` | string | `one-piece` |

      | `livechart` | integer | `321` |

      | `letterboxd` | string | `the-truman-show` |

      | `netflix` | integer | `70210890` (movie ID) |

      | `traktslug` | string | `john-wick-chapter-4-2023` |


      > 📖 Full reference: [Standard media
      objects](/conventions/standard-media-objects)


      <!-- IDS_TABLE_END -->
    externalDocs:
      description: Sync guide
      url: https://api.simkl.org/guides/sync
  - name: Users
    description: User profile, settings, and watch statistics.
externalDocs:
  description: Simkl API documentation
  url: https://api.simkl.org
paths:
  /sync/watched:
    post:
      tags:
        - Sync
      summary: Look up watched status for items
      description: >-
        POST an array of items you already know about; Simkl returns a parallel
        array telling you, **per item**, whether it's in the user's library, its
        current status, last-watched timestamp, and (optionally) per-episode
        breakdown.


        Use this **only** when you don't already cache the user's full library
        locally — typical case is a media-server plugin or a deep-link landing
        page that needs to check "is this title in the user's tracker yet?" for
        a handful of specific titles, without syncing the whole library first.


        > ⚠️ **Don't use this endpoint if your app already pulls [`GET
        /sync/all-items/{type}/{status}`](/api-reference/simkl/get-all-items).**
        The full-library response already contains the same per-item watch
        state, statuses, and last-watched timestamps that `/sync/watched`
        returns — your local cache has the answer. Calling both is wasted
        requests, counts twice against your rate-limit quota, and is one of the
        patterns that gets an app's `client_id` suspended. The correct loop for
        tracker apps that sync the full library is the two-phase model: full
        pull once, then `/sync/activities`-gated incremental refresh — see the
        [Sync guide](/guides/sync).


        #### Item identification


        Each input item carries one or more IDs. Simkl resolves to the canonical
        record before looking up watch state, so any of these work:


        | ID style | Example |

        |---|---|

        | Simkl ID | `{ "ids": { "simkl": 2090 } }` |

        | IMDb / TMDB / TVDB / MAL / AniDB / AniList / Kitsu | `{ "ids": {
        "imdb": "tt1520211" } }` |

        | Title + year fallback | `{ "title": "Inception", "year": 2010 }` |


        Pair `season` + `episode` on an item to ask 'has the user watched this
        specific episode?' instead of 'is this title in the library?'.


        #### Query params


        | Param | Effect |

        |---|---|

        | `extended=episodes` | Include per-episode breakdown
        (`seasons[].episodes[]` arrays) for shows/anime. **Limit: 100 items per
        call** when this is set — sending more triggers `400 max_items`. |

        | `extended=specials` | Include specials (season `0`). Only effective
        when combined with `episodes`. |

        | `extended=counters` | When sent **alone** (without `episodes`), the
        `seasons[]` array is included but its `episodes[]` arrays are omitted —
        useful when you only want totals without the per-episode payload. When
        sent **together with `episodes`**, the per-episode arrays are still
        included. |


        Multiple values are comma-separated: `extended=episodes,specials`.


        #### Response shape (per item)


        The response is an array of the same length as the request, in the same
        order. Each entry echoes the input identifiers and adds:


        | Field | When present | Notes |

        |---|---|---|

        | `result` | always | `true` if the user has watched (or is watching)
        this item. `false` if Simkl matched the IDs but the item isn't in the
        user's library. `"not_found"` if Simkl couldn't match the IDs at all —
        in this case only `result` is returned, no `simkl`/`list`/etc. |

        | `simkl` | when `result` ≠ `"not_found"` | Canonical Simkl ID. |

        | `list` | when matched | Current watchlist status (`watching`,
        `completed`, `plantowatch`, `hold`, `dropped`) or `null` if not in any
        list. |

        | `last_watched_at` | when matched | ISO-8601 timestamp of the most
        recent watch event, or `null` if never watched. |

        | `episodes_total` / `episodes_aired` / `episodes_to_be_aired` /
        `episodes_watched` | with `extended=episodes` or `extended=counters`
        (shows/anime only) | Aggregate counts across all seasons. |

        | `seasons[]` | with `extended=episodes` or `extended=counters`
        (shows/anime only) | Per-season `{number, episodes_total,
        episodes_aired, episodes_to_be_aired, episodes_watched}`. With
        `extended=episodes` alone, each season also includes an `episodes[]`
        array (per-episode `{number, watched, aired, last_watched_at}`). With
        `extended=counters` alone, `episodes[]` is omitted. |


        **Empty body quirk.** Sending an empty array `[]` returns the literal
        `null` (not `[]`). Treat both as 'no items to check'.


        #### Errors


        | Status | `error` | When |

        |---|---|---|

        | 400 | `max_items` | More than 100 items in a single call when
        `extended=episodes` (or any other `extended` value that triggers
        per-episode loading) is set. |


        <Card title="Sync guide — full walkthrough" icon="arrows-rotate"
        href="/guides/sync" horizontal>
         Two-phase model (initial pull → activities-checked delta loop), `date_from` semantics, when to use `/sync/watched` vs `/sync/all-items`, and reference implementations.
        </Card>


        <Tip>

        **Which IDs can I send/expect?** All accepted input identifiers and the
        keys you'll see echoed back in responses are listed at [**Standard media
        objects → Supported ID
        keys**](/conventions/standard-media-objects#supported-id-keys). Send
        every ID you have on writes — Simkl picks the first that resolves and
        ignores the rest. Reminder: `slug` is **response-only** (never send it
        on a request).

        </Tip>
      operationId: post-sync-watched
      parameters:
        - name: extended
          in: query
          description: >-
            Comma-separated combination of `counters`, `episodes`, `specials`.
            Order does not matter; empty or unknown values are ignored.
            Examples: `counters`, `episodes,specials`,
            `counters,episodes,specials`.
          schema:
            type: string
            pattern: ^(counters|episodes|specials)(,(counters|episodes|specials))*$
          example: episodes,specials
        - $ref: '#/components/parameters/ClientIdQuery'
        - $ref: '#/components/parameters/AppNameQuery'
        - $ref: '#/components/parameters/AppVersionQuery'
        - $ref: '#/components/parameters/UserAgentHeader'
      requestBody:
        content:
          application/json:
            examples:
              one_movie_in_library:
                summary: Single movie — known to Simkl but not in user's library
                value:
                  - ids:
                      simkl: 53536
              one_show_in_library:
                summary: Single show — in user's library, completed
                value:
                  - ids:
                      simkl: 2090
              unknown_id_not_found:
                summary: 'ID couldn''t be resolved — minimal `result: not_found` shape'
                value:
                  - ids:
                      simkl: 99999991
              bulk_mixed_results:
                summary: 'Three items: known-but-not-watched, not_found, in-library'
                value:
                  - ids:
                      simkl: 53536
                  - ids:
                      simkl: 99999992
                  - ids:
                      simkl: 17465
              show_with_full_episodes:
                summary: Show with `extended=episodes` — full per-episode breakdown
                value:
                  - ids:
                      simkl: 2090
              show_with_counters_only:
                summary: >-
                  Show with `extended=counters` — totals but no per-episode
                  arrays
                value:
                  - ids:
                      simkl: 2090
              specific_episode_lookup:
                summary: Per-episode lookup via `season` + `episode` on the input item
                value:
                  - ids:
                      simkl: 2090
                    season: 1
                    episode: 1
              title_year_fallback:
                summary: >-
                  Title + year fallback (no IDs) — Simkl resolves to canonical
                  record
                value:
                  - title: Inception
                    year: 2010
            schema:
              $ref: '#/components/schemas/WatchedLookupRequest'
      responses:
        '200':
          description: Array of per-item watch results in input order.
          headers: {}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WatchedLookupResponse'
              examples:
                one_movie_in_library:
                  summary: Single movie — known to Simkl but not in user's library
                  value:
                    - ids:
                        simkl: 53536
                      result: false
                      simkl: 53536
                      list: null
                      last_watched_at: null
                one_show_in_library:
                  summary: Single show — in user's library, completed
                  value:
                    - ids:
                        simkl: 2090
                      result: true
                      simkl: 2090
                      list: completed
                      last_watched_at: '2026-05-15T00:35:15Z'
                unknown_id_not_found:
                  summary: 'ID couldn''t be resolved — minimal `result: not_found` shape'
                  value:
                    - ids:
                        simkl: 99999991
                      result: not_found
                bulk_mixed_results:
                  summary: 'Three items: known-but-not-watched, not_found, in-library'
                  value:
                    - ids:
                        simkl: 53536
                      result: false
                      simkl: 53536
                      list: null
                      last_watched_at: null
                    - ids:
                        simkl: 99999992
                      result: not_found
                    - ids:
                        simkl: 17465
                      result: true
                      simkl: 17465
                      list: completed
                      last_watched_at: '2026-05-15T00:13:18Z'
                show_with_full_episodes:
                  summary: Show with `extended=episodes` — full per-episode breakdown
                  value:
                    - ids:
                        simkl: 2090
                      result: true
                      simkl: 2090
                      list: completed
                      last_watched_at: '2026-05-15T00:35:15Z'
                      episodes_total: 177
                      episodes_aired: 177
                      episodes_to_be_aired: 0
                      episodes_watched: 177
                      seasons:
                        - number: 1
                          episodes_total: 6
                          episodes_aired: 6
                          episodes_to_be_aired: 0
                          episodes_watched: 6
                          episodes:
                            - number: 1
                              watched: true
                              aired: true
                              last_watched_at: null
                            - number: 2
                              watched: true
                              aired: true
                              last_watched_at: null
                            - number: 3
                              watched: true
                              aired: true
                              last_watched_at: null
                show_with_counters_only:
                  summary: >-
                    Show with `extended=counters` — totals but no per-episode
                    arrays
                  value:
                    - ids:
                        simkl: 2090
                      result: true
                      simkl: 2090
                      list: completed
                      last_watched_at: '2026-05-15T00:35:15Z'
                      episodes_total: 177
                      episodes_aired: 177
                      episodes_to_be_aired: 0
                      episodes_watched: 177
                      seasons:
                        - number: 1
                          episodes_total: 6
                          episodes_aired: 6
                          episodes_to_be_aired: 0
                          episodes_watched: 6
                specific_episode_lookup:
                  summary: >-
                    Per-episode lookup via `season` + `episode` on the input
                    item
                  value:
                    - ids:
                        simkl: 2090
                      season: 1
                      episode: 1
                      result: true
                      simkl: 2090
                      list: completed
                      last_watched_at: '2026-05-15T00:35:15Z'
                title_year_fallback:
                  summary: >-
                    Title + year fallback (no IDs) — Simkl resolves to canonical
                    record
                  value:
                    - title: Inception
                      year: 2010
                      result: false
                      simkl: 472214
                      list: null
                      last_watched_at: null
        '400':
          description: >-
            Bad request — most commonly fired when `extended=episodes` (or any
            extended value that triggers per-episode loading) is combined with
            more than 100 items in a single call.
          content:
            application/json:
              schema:
                $ref: 093bfedf-7abd-4ee1-aaf6-c6c88a2c3029
              example:
                error: max_items
                code: 400
        '401':
          $ref: '#/components/responses/Unauthorized'
        '412':
          $ref: '#/components/responses/ClientIdFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      security:
        - clientId: []
          bearerAuth: []
        - simklApiKey: []
          bearerAuth: []
components:
  parameters:
    ClientIdQuery:
      name: client_id
      in: query
      required: true
      description: >-
        Your **`client_id`** from your [Simkl developer
        settings](https://simkl.com/settings/developer/). Required on every
        request.
      schema:
        type: string
      example: YOUR_CLIENT_ID
    AppNameQuery:
      name: app-name
      in: query
      required: true
      description: >-
        Short, lowercase identifier for your app (e.g. `plex-scrobbler`,
        `kodi-bridge`). Helps Simkl identify which apps are using the API.
      schema:
        type: string
      example: my-app
    AppVersionQuery:
      name: app-version
      in: query
      required: true
      description: >-
        Your app's current version (e.g. `1.0`, `2.4.1`). Helps Simkl debug
        issues you report.
      schema:
        type: string
      example: '1.0'
    UserAgentHeader:
      name: User-Agent
      in: header
      required: true
      description: >-
        Descriptive identifier for your app, ideally `name/version`. Examples:
        `PlexMediaServer/1.43.1.10540`, `kodi-simkl/0.9.2`, `MyApp/2.4.1
        (https://myapp.com)`.
      schema:
        type: string
      example: my-app/1.0
  schemas:
    WatchedLookupRequest:
      type: array
      description: Array of items to look up. Each item carries one or more external IDs.
      items:
        $ref: '#/components/schemas/WatchedLookupItem'
    WatchedLookupResponse:
      type: array
      description: >-
        One result per input item, in input order. See the operation description
        for the full per-item shape and how the optional episode-breakdown
        fields are gated on the `extended` query parameter.
      items:
        type: object
        description: >-
          One result, in the same order as the request array. Echoes the input
          identifiers (so you can correlate without index gymnastics) and adds
          the watch-status fields.
        properties:
          result:
            description: >-
              `true` if the user has watched (or is watching) the item, `false`
              if Simkl matched the IDs but the item isn't in the user's library,
              or the literal string `"not_found"` if Simkl couldn't resolve the
              IDs to any catalog entry.
            oneOf:
              - type: boolean
              - type: string
                enum:
                  - not_found
            example: true
          simkl:
            type: integer
            description: 'Canonical Simkl ID. Omitted when `result: "not_found"`.'
            example: 2090
          list:
            type:
              - string
              - 'null'
            enum:
              - watching
              - completed
              - plantowatch
              - hold
              - dropped
              - null
            description: >-
              Current watchlist status. **Type 4 null** — `null` when the item
              resolved to a Simkl record but isn't in any of the user's lists.
              See [Null and missing values](/conventions/null-values).
            example: completed
          last_watched_at:
            type:
              - string
              - 'null'
            format: date-time
            description: >-
              ISO-8601 timestamp of the most recent watch event for this item.
              **Type 4 null** — `null` if the item has never been watched. See
              [Null and missing values](/conventions/null-values).
            example: '2026-05-15T00:35:15Z'
          episodes_total:
            type: integer
            description: >-
              Present only with `extended=episodes` or `extended=counters`
              (shows/anime).
            example: 177
          episodes_aired:
            type: integer
            description: Present only with `extended=episodes` or `extended=counters`.
            example: 177
          episodes_to_be_aired:
            type: integer
            description: Present only with `extended=episodes` or `extended=counters`.
            example: 0
          episodes_watched:
            type: integer
            description: Present only with `extended=episodes` or `extended=counters`.
            example: 177
          seasons:
            type: array
            description: >-
              Per-season breakdown. Present only with `extended=episodes` or
              `extended=counters` (shows/anime). With `extended=counters` alone,
              the inner `episodes[]` array is omitted.
            items:
              type: object
              properties:
                number:
                  type: integer
                  example: 1
                episodes_total:
                  type: integer
                  example: 6
                episodes_aired:
                  type: integer
                  example: 6
                episodes_to_be_aired:
                  type: integer
                  example: 0
                episodes_watched:
                  type: integer
                  example: 6
                episodes:
                  type: array
                  description: >-
                    Per-episode array. Present with `extended=episodes`, omitted
                    with `extended=counters` alone.
                  items:
                    type: object
                    properties:
                      number:
                        type: integer
                        example: 1
                      watched:
                        type: boolean
                        example: true
                      aired:
                        type: boolean
                        example: true
                      last_watched_at:
                        type:
                          - string
                          - 'null'
                        format: date-time
                        description: >-
                          Per-episode last-watched timestamp. **Type 4 null** —
                          `null` if the user has marked the title completed but
                          Simkl has no specific per-episode timestamp. See [Null
                          and missing values](/conventions/null-values).
                        example: null
        additionalProperties: true
    WatchedLookupItem:
      type: object
      description: >-
        An item to look up — pass any one or more external IDs (Simkl resolves
        to canonical record). For per-episode watched-status lookup, also pair
        `season` + `episode`.
      allOf:
        - $ref: '#/components/schemas/Ids'
        - type: object
          properties:
            type:
              type: string
              enum:
                - show
                - movie
                - anime
              description: >-
                Item type. Helps the lookup find the right title when the same
                external ID could match multiple types. Optional but
                recommended.
              example: show
            season:
              type: integer
              minimum: 0
              description: >-
                For episode-level lookup: which season. Pair with `episode`.
                Per-episode results are returned alongside the title-level
                `result` field.
              example: 1
            episode:
              type: integer
              minimum: 1
              description: >-
                For episode-level lookup: which episode within the `season`.
                Pair with `season`. Result reflects whether the user has watched
                that specific episode.
              example: 3
    ErrorResponse:
      type: object
      description: >-
        Standard error envelope for 4xx and 5xx responses. Branch on `error`
        (machine-readable identifier) — `message`, when present, is
        human-readable guidance and is **not stable across releases**.
      properties:
        error:
          type: string
          description: >-
            Machine-readable error identifier. Stable across responses — use
            this for programmatic branching in client code. Examples:
            `user_token_failed`, `client_id_failed`, `empty_field`,
            `wrong_parameter`, `id_err`, `rate_limit`.
          example: user_token_failed
        code:
          type: integer
          description: >-
            HTTP status code echoed in the body for convenience — same value as
            the response status line. Always an integer.
          example: 401
        message:
          type: string
          description: >-
            Human-readable error guidance. Optional — present on most errors,
            but not guaranteed. May reference the specific field or value that
            triggered the error. NOT stable; do not parse.
          example: Your client_id is wrong. Try another one
      required:
        - error
        - code
    Ids:
      type: object
      description: >-
        External and internal identifiers for an item. Pass as many as you have
        — Simkl resolves to the canonical record.
      properties:
        simkl:
          type: integer
          description: Simkl internal ID. Most reliable.
          example: 53536
        slug:
          type: string
          description: URL-safe slug returned in responses.
          example: attack-on-titan
        imdb:
          type: string
          description: IMDb ID.
          example: tt0181852
        tmdb:
          type: string
          description: TMDb ID.
          example: '296'
        tvdb:
          oneOf:
            - type: integer
            - type: string
          description: TVDB ID or slug.
          example: 153021
        mal:
          type: string
          description: MyAnimeList ID.
          example: '4246'
        anidb:
          type: string
          description: AniDB ID. Specifying just this is enough for anime lookups.
          example: '10846'
        anilist:
          type: string
          description: AniList ID.
          example: '21'
        kitsu:
          type: string
          description: Kitsu ID.
          example: '12'
        anisearch:
          type: string
          description: aniSearch ID.
          example: '2227'
        animeplanet:
          type: string
          description: Anime-Planet slug.
          example: one-piece
        livechart:
          type: string
          description: LiveChart ID.
          example: '321'
        letterboxd:
          type: string
          description: Letterboxd slug.
          example: the-truman-show
        netflix:
          type: string
          description: Netflix movie ID.
          example: '70210890'
        hulu:
          type: string
          description: Hulu episode ID.
        crunchyroll:
          type: string
          description: Crunchyroll episode ID.
        traktslug:
          type: string
          description: Trakt slug.
          example: john-wick-chapter-4-2023
      additionalProperties: true
      example:
        simkl: 53536
        imdb: tt0181852
        tmdb: 296
  responses:
    Unauthorized:
      description: >-
        Missing or invalid user access token. Provide a valid `Authorization:
        Bearer <access_token>` header.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: user_token_failed
            code: 401
    ClientIdFailed:
      description: >-
        Your `client_id` is missing, wrong, disabled, or has hit a request
        limit. Verify in your [developer
        settings](https://simkl.com/settings/developer/).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: client_id_failed
            code: 412
            message: Your client_id is wrong. Try another one
    RateLimited:
      description: >-
        Too many requests in too short a window. Back off and retry with
        exponential backoff — see [Rate limits](/resources/rate-limits) for the
        playbook.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: rate_limit
            code: 429
    ServerError:
      description: >-
        Something is broken on Simkl's side. Retry after a short delay. If it
        persists, report on the [Simkl Discord](https://discord.gg/MJsWNE4).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: internal
            code: 500
  securitySchemes:
    clientId:
      type: apiKey
      in: query
      name: client_id
      description: >-
        Preferred form: your `client_id` as a URL query parameter on every
        request. Self-describing in logs and curl commands. See [Headers and
        required parameters](/conventions/headers).
      x-default: YOUR_CLIENT_ID
    simklApiKey:
      type: apiKey
      in: header
      name: simkl-api-key
      description: >-
        Optional alias for the `client_id` query parameter. Simkl accepts your
        `client_id` either as the `simkl-api-key` request header **or** as the
        `?client_id=…` query parameter — pick one. The query-parameter form is
        preferred because it makes the request fully self-describing in URL
        form.
      x-default: YOUR_CLIENT_ID
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        OAuth 2.0 or PIN-flow `access_token`. Required for endpoints that read
        or modify the user's library, scrobble session, ratings, settings, or
        playbacks. See [Authentication](/authentication).
      x-default: YOUR_ACCESS_TOKEN

````