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

# Add Ratings

> Apply user ratings (1-10) to movies, shows, or anime. Same auth model and batching rules as the rest of the [Sync API](/guides/sync). To **read** ratings back, use [`GET /sync/ratings/:type/:rating`](/api-reference/simkl/get-user-ratings) — see [Read side](#read-side) below.

#### Body shape

Top-level keys per media type, each carrying an array of items:

```json
{
  "movies": [
    {
      "rating": 8,
      "ids": {
        "simkl": 53536
      }
    }
  ],
  "shows": [
    {
      "rating": 9,
      "ids": {
        "tmdb": "1399"
      }
    }
  ],
  "anime": [
    {
      "rating": 10,
      "ids": {
        "mal": "11757"
      },
      "rated_at": "2026-05-15T20:00:00Z"
    }
  ]
}
```

Per-item fields:

| Field      | Type    | Required | Notes |
|---|---|---|---|
| `rating`   | int 1-10 | yes | Out-of-range values (`0`, `11`, negatives) are **silently ignored** — see [Out-of-range](#out-of-range-ratings) below. |
| `ids`      | object  | yes | Any [supported ID](/conventions/standard-media-objects#supported-id-keys): `simkl`, `imdb`, `tmdb`, `tvdb`, `mal`, `anidb`, `anilist`, `kitsu`, `livechart`, `anisearch`, `animeplanet`. Plus optional `title`+`year` fallback. |
| `rated_at` | ISO-8601 | no | Defaults to "now". Use to back-date imports from another tracker. |

Re-rating an item **overwrites** the prior value — no need to call `/sync/ratings/remove` first.

#### Auto-move side effect

Rating an item that's not yet on the user's list **auto-files it** based on airing status:

| Item kind                                                    | New status     |
|---|---|
| Released movie                                                | `completed`    |
| Unreleased / upcoming movie                                   | `plantowatch`  |
| Single-episode show                                           | `completed`    |
| Multi-episode show or anime (any other case)                  | `watching`     |

The corresponding list timestamp on [`/sync/activities`](/api-reference/simkl/get-activities) bumps, so a rated item shows up in the next `date_from` delta even though the user only rated it. Treat the delta as authoritative.

#### Response (201 Created)

```json
{
  "added": {
    "movies": 1,
    "shows": 1,
    "statuses": [
      {
        "request": {
          "rating": 8,
          "ids": {
            "simkl": 53536
          }
        },
        "response": {
          "status": "completed"
        }
      }
    ]
  },
  "not_found": {
    "movies": [],
    "shows": []
  }
}
```

**`added.anime` does NOT exist.** Anime items are folded into the `shows` counter — apps must not look for a separate `anime` slot in `added` or `not_found`. If you need to know which items landed, walk `added.statuses[]` (the `request.ids` echo back what you sent).

`response.status` per item is the watchlist status the auto-move applied (`completed`, `watching`, `plantowatch`, etc.) — useful for updating local UI without a follow-up `/sync/activities` poll.

#### Out-of-range ratings

Any `rating` outside 1-10 (including `0`, `11`, `-1`, `100`) is **silently rejected** — the item lands in `not_found.<type>` and the HTTP status is still `201`. No `400` is returned; the rejection is reported in the response body, not the status code. **Clients must validate client-side**; never trust that a 2xx response means the rating was applied. Always inspect `added.statuses[]` (or `not_found`) to confirm.

#### Read side

`GET /sync/ratings` returns every rated item across all types in one response, keyed by media type:

```json
{
  "movies": [ { ..., "user_rating": 8, "user_rated_at": "2026-05-13T..." } ],
  "shows":  [ { ..., "user_rating": 9, ... } ],
  "anime":  [ { ..., "user_rating": 10, ... } ]
}
```

Each item carries the standard watchlist record (status, episode counts, dates) plus `user_rating` (int 1-10 or `null`) and `user_rated_at` (ISO-8601 UTC or `null`).

Read ratings back with [`GET /sync/ratings/:type/:rating`](/api-reference/simkl/get-user-ratings). For **public catalog** ratings (the community average + IMDb/MAL score for any title, no token required), the rating data is in the per-title detail endpoints — [`GET /movies/:id`](/api-reference/simkl/get-movie), [`GET /tv/:id`](/api-reference/simkl/get-tv-show), [`GET /anime/:id`](/api-reference/simkl/get-anime) — under the `ratings` field. Resolve external IDs first via [`GET /redirect`](/api-reference/simkl/redirect).

#### Removing a rating

Use [`POST /sync/ratings/remove`](/api-reference/simkl/remove-ratings) with the same body shape minus the `rating` field (the value is ignored on remove). Removing the rating does **not** remove the item from the user's watchlist — only the score is cleared.

#### Rate alongside a watch event

If you're recording a watch event and want to attach a rating in the same call, use [`POST /sync/history`](/api-reference/simkl/add-to-history) — it accepts a `rating` field per item. One round-trip instead of two.

<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, deletion reconciliation, edge cases, and reference implementations in Node and Python.
</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/ratings
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/ratings:
    post:
      tags:
        - Sync
      summary: Add Ratings
      description: >-
        Apply user ratings (1-10) to movies, shows, or anime. Same auth model
        and batching rules as the rest of the [Sync API](/guides/sync). To
        **read** ratings back, use [`GET
        /sync/ratings/:type/:rating`](/api-reference/simkl/get-user-ratings) —
        see [Read side](#read-side) below.


        #### Body shape


        Top-level keys per media type, each carrying an array of items:


        ```json

        {
          "movies": [
            {
              "rating": 8,
              "ids": {
                "simkl": 53536
              }
            }
          ],
          "shows": [
            {
              "rating": 9,
              "ids": {
                "tmdb": "1399"
              }
            }
          ],
          "anime": [
            {
              "rating": 10,
              "ids": {
                "mal": "11757"
              },
              "rated_at": "2026-05-15T20:00:00Z"
            }
          ]
        }

        ```


        Per-item fields:


        | Field      | Type    | Required | Notes |

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

        | `rating`   | int 1-10 | yes | Out-of-range values (`0`, `11`,
        negatives) are **silently ignored** — see
        [Out-of-range](#out-of-range-ratings) below. |

        | `ids`      | object  | yes | Any [supported
        ID](/conventions/standard-media-objects#supported-id-keys): `simkl`,
        `imdb`, `tmdb`, `tvdb`, `mal`, `anidb`, `anilist`, `kitsu`, `livechart`,
        `anisearch`, `animeplanet`. Plus optional `title`+`year` fallback. |

        | `rated_at` | ISO-8601 | no | Defaults to "now". Use to back-date
        imports from another tracker. |


        Re-rating an item **overwrites** the prior value — no need to call
        `/sync/ratings/remove` first.


        #### Auto-move side effect


        Rating an item that's not yet on the user's list **auto-files it** based
        on airing status:


        | Item kind                                                    | New
        status     |

        |---|---|

        | Released movie                                                |
        `completed`    |

        | Unreleased / upcoming movie                                   |
        `plantowatch`  |

        | Single-episode show                                           |
        `completed`    |

        | Multi-episode show or anime (any other case)                  |
        `watching`     |


        The corresponding list timestamp on
        [`/sync/activities`](/api-reference/simkl/get-activities) bumps, so a
        rated item shows up in the next `date_from` delta even though the user
        only rated it. Treat the delta as authoritative.


        #### Response (201 Created)


        ```json

        {
          "added": {
            "movies": 1,
            "shows": 1,
            "statuses": [
              {
                "request": {
                  "rating": 8,
                  "ids": {
                    "simkl": 53536
                  }
                },
                "response": {
                  "status": "completed"
                }
              }
            ]
          },
          "not_found": {
            "movies": [],
            "shows": []
          }
        }

        ```


        **`added.anime` does NOT exist.** Anime items are folded into the
        `shows` counter — apps must not look for a separate `anime` slot in
        `added` or `not_found`. If you need to know which items landed, walk
        `added.statuses[]` (the `request.ids` echo back what you sent).


        `response.status` per item is the watchlist status the auto-move applied
        (`completed`, `watching`, `plantowatch`, etc.) — useful for updating
        local UI without a follow-up `/sync/activities` poll.


        #### Out-of-range ratings


        Any `rating` outside 1-10 (including `0`, `11`, `-1`, `100`) is
        **silently rejected** — the item lands in `not_found.<type>` and the
        HTTP status is still `201`. No `400` is returned; the rejection is
        reported in the response body, not the status code. **Clients must
        validate client-side**; never trust that a 2xx response means the rating
        was applied. Always inspect `added.statuses[]` (or `not_found`) to
        confirm.


        #### Read side


        `GET /sync/ratings` returns every rated item across all types in one
        response, keyed by media type:


        ```json

        {
          "movies": [ { ..., "user_rating": 8, "user_rated_at": "2026-05-13T..." } ],
          "shows":  [ { ..., "user_rating": 9, ... } ],
          "anime":  [ { ..., "user_rating": 10, ... } ]
        }

        ```


        Each item carries the standard watchlist record (status, episode counts,
        dates) plus `user_rating` (int 1-10 or `null`) and `user_rated_at`
        (ISO-8601 UTC or `null`).


        Read ratings back with [`GET
        /sync/ratings/:type/:rating`](/api-reference/simkl/get-user-ratings).
        For **public catalog** ratings (the community average + IMDb/MAL score
        for any title, no token required), the rating data is in the per-title
        detail endpoints — [`GET /movies/:id`](/api-reference/simkl/get-movie),
        [`GET /tv/:id`](/api-reference/simkl/get-tv-show), [`GET
        /anime/:id`](/api-reference/simkl/get-anime) — under the `ratings`
        field. Resolve external IDs first via [`GET
        /redirect`](/api-reference/simkl/redirect).


        #### Removing a rating


        Use [`POST /sync/ratings/remove`](/api-reference/simkl/remove-ratings)
        with the same body shape minus the `rating` field (the value is ignored
        on remove). Removing the rating does **not** remove the item from the
        user's watchlist — only the score is cleared.


        #### Rate alongside a watch event


        If you're recording a watch event and want to attach a rating in the
        same call, use [`POST
        /sync/history`](/api-reference/simkl/add-to-history) — it accepts a
        `rating` field per item. One round-trip instead of two.


        <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, deletion reconciliation, edge cases, and reference implementations in Node and Python.
        </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-ratings
      parameters:
        - $ref: '#/components/parameters/ClientIdQuery'
        - $ref: '#/components/parameters/AppNameQuery'
        - $ref: '#/components/parameters/AppVersionQuery'
        - $ref: '#/components/parameters/UserAgentHeader'
      requestBody:
        content:
          application/json:
            example:
              movies:
                - title: 'Terminator 3: Rise of the Machines'
                  rating: 8
                  rated_at: '2014-09-01T09:10:11.000Z'
                  year: '2003'
                  ids:
                    imdb: tt0181852
                    tmdb: 296
                    simkl: 53536
                - rating: 6
                  ids:
                    simkl: 210728
              shows:
                - title: Attack on Titan
                  year: 2013
                  rating: 10
                  ids:
                    simkl: 39687
                    mal: 16498
                    tvdb: 267440
                    imdb: tt2560140
                    anidb: 9541
            schema:
              $ref: '#/components/schemas/RatingsAddRequest'
            examples:
              bulk_rate_cross_media_types:
                summary: Bulk rate across all three media types in one request
                description: >-
                  The body can carry `movies`, `shows`, and `anime` arrays
                  simultaneously — useful when importing ratings from another
                  tracker.
                value:
                  movies:
                    - rating: 7
                      ids:
                        simkl: 752138
                  shows:
                    - rating: 9
                      ids:
                        simkl: 2090
                  anime:
                    - rating: 8
                      ids:
                        simkl: 831411
                        mal: 38000
                        anidb: 14116
                        anilist: 101922
              overwrite_movie_rating:
                summary: Overwrite an existing rating (5 → 9)
                description: >-
                  Re-POSTing the same item with a different `rating` replaces
                  the stored value. No need to call /sync/ratings/remove first.
                value:
                  movies:
                    - rating: 9
                      ids:
                        simkl: 752138
              quirk_out_of_range_rating_-1:
                summary: 'Quirk: server accepts rating -1 (out of 1-10 range)'
                description: >-
                  The server returned status 201 for `rating: -1`. Stored value
                  lookup: None. Validate ratings client-side; the server does
                  not enforce the documented 1-10 range.
                value:
                  movies:
                    - rating: -1
                      ids:
                        simkl: 752138
              quirk_out_of_range_rating_0:
                summary: 'Quirk: server accepts rating 0 (out of 1-10 range)'
                description: >-
                  The server returned status 201 for `rating: 0`. Stored value
                  lookup: None. Validate ratings client-side; the server does
                  not enforce the documented 1-10 range.
                value:
                  movies:
                    - rating: 0
                      ids:
                        simkl: 752138
              quirk_out_of_range_rating_100:
                summary: 'Quirk: server accepts rating 100 (out of 1-10 range)'
                description: >-
                  The server returned status 201 for `rating: 100`. Stored value
                  lookup: None. Validate ratings client-side; the server does
                  not enforce the documented 1-10 range.
                value:
                  movies:
                    - rating: 100
                      ids:
                        simkl: 752138
              quirk_out_of_range_rating_11:
                summary: 'Quirk: server accepts rating 11 (out of 1-10 range)'
                description: >-
                  The server returned status 201 for `rating: 11`. Stored value
                  lookup: None. Validate ratings client-side; the server does
                  not enforce the documented 1-10 range.
                value:
                  movies:
                    - rating: 11
                      ids:
                        simkl: 752138
              rate_anime_8:
                summary: Rate an anime 8/10
                description: >-
                  Anime use the `anime` array key in the REQUEST. (Note: in the
                  /sync/ratings/{type}/{rating} READ response, anime are wrapped
                  in `show:` for cross-catalog compatibility — see
                  /conventions/null-values.)
                value:
                  anime:
                    - rating: 8
                      ids:
                        simkl: 831411
                        mal: 38000
                        anidb: 14116
                        anilist: 101922
              rate_movie_1:
                summary: Rate a movie 1/10
                description: >-
                  Standard rating shape. The server stores the value verbatim
                  (no rounding). Re-POSTing with a different rating overwrites;
                  there's no need to call /sync/ratings/remove first.
                value:
                  movies:
                    - rating: 1
                      ids:
                        simkl: 752138
              rate_movie_10:
                summary: Rate a movie 10/10
                description: >-
                  Standard rating shape. The server stores the value verbatim
                  (no rounding). Re-POSTing with a different rating overwrites;
                  there's no need to call /sync/ratings/remove first.
                value:
                  movies:
                    - rating: 10
                      ids:
                        simkl: 752138
              rate_movie_5:
                summary: Rate a movie 5/10
                description: >-
                  Standard rating shape. The server stores the value verbatim
                  (no rounding). Re-POSTing with a different rating overwrites;
                  there's no need to call /sync/ratings/remove first.
                value:
                  movies:
                    - rating: 5
                      ids:
                        simkl: 752138
              rate_movie_8:
                summary: Rate a movie 8/10
                description: >-
                  Standard rating shape. The server stores the value verbatim
                  (no rounding). Re-POSTing with a different rating overwrites;
                  there's no need to call /sync/ratings/remove first.
                value:
                  movies:
                    - rating: 8
                      ids:
                        simkl: 752138
              rate_show_9:
                summary: Rate a TV show 9/10
                description: Same shape as movies; just swap the array key to `shows`.
                value:
                  shows:
                    - rating: 9
                      ids:
                        simkl: 2090
        required: true
      responses:
        '200':
          description: OK
          headers: {}
          content:
            application/json:
              example:
                added:
                  movies: 2
                  shows: 1
                  statuses:
                    - request:
                        title: 'Terminator 3: Rise of the Machines'
                        year: '2003'
                        rated_at: '2014-09-01T09:10:11.000Z'
                        rating: 8
                        ids:
                          imdb: tt0181852
                          tmdb: 296
                          simkl: 53536
                      response:
                        status: completed
                    - request:
                        rating: 6
                        ids:
                          simkl: 210728
                      response:
                        status: completed
                    - request:
                        title: Attack on Titan
                        year: 2013
                        rating: 10
                        ids:
                          simkl: 39687
                          mal: 16498
                          tvdb: 267440
                          imdb: tt2560140
                          anidb: 9541
                      response:
                        status: watching
                not_found:
                  movies: []
                  shows: []
              schema:
                $ref: '#/components/schemas/RatingsAddResponse'
        '201':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RatingsAddResponse'
              examples:
                bulk_rate_cross_media_types:
                  summary: Bulk rate across all three media types in one request
                  description: >-
                    The body can carry `movies`, `shows`, and `anime` arrays
                    simultaneously — useful when importing ratings from another
                    tracker.
                  value:
                    added:
                      movies: 1
                      shows: 2
                      statuses:
                        - request:
                            rating: 7
                            ids:
                              simkl: 752138
                            type: movie
                          response:
                            status: completed
                        - request:
                            rating: 9
                            ids:
                              simkl: 2090
                            type: show
                          response:
                            status: watching
                        - request:
                            rating: 8
                            ids:
                              simkl: 831411
                              mal: 38000
                              anidb: 14116
                              anilist: 101922
                            type: show
                          response:
                            status: watching
                    not_found:
                      movies: []
                      shows: []
                overwrite_movie_rating:
                  summary: Overwrite an existing rating (5 → 9)
                  description: >-
                    Re-POSTing the same item with a different `rating` replaces
                    the stored value. No need to call /sync/ratings/remove
                    first.
                  value:
                    added:
                      movies: 1
                      shows: 0
                      statuses:
                        - request:
                            rating: 9
                            ids:
                              simkl: 752138
                            type: movie
                          response:
                            status: completed
                    not_found:
                      movies: []
                      shows: []
                quirk_out_of_range_rating_-1:
                  summary: 'Quirk: server accepts rating -1 (out of 1-10 range)'
                  description: >-
                    The server returned status 201 for `rating: -1`. Stored
                    value lookup: None. Validate ratings client-side; the server
                    does not enforce the documented 1-10 range.
                  value:
                    added:
                      movies: 0
                      shows: 0
                      statuses: []
                    not_found:
                      movies:
                        - rating: -1
                          ids:
                            simkl: 752138
                          type: movie
                      shows: []
                quirk_out_of_range_rating_0:
                  summary: 'Quirk: server accepts rating 0 (out of 1-10 range)'
                  description: >-
                    The server returned status 201 for `rating: 0`. Stored value
                    lookup: None. Validate ratings client-side; the server does
                    not enforce the documented 1-10 range.
                  value:
                    added:
                      movies: 0
                      shows: 0
                      statuses: []
                    not_found:
                      movies:
                        - rating: 0
                          ids:
                            simkl: 752138
                          type: movie
                      shows: []
                quirk_out_of_range_rating_100:
                  summary: 'Quirk: server accepts rating 100 (out of 1-10 range)'
                  description: >-
                    The server returned status 201 for `rating: 100`. Stored
                    value lookup: None. Validate ratings client-side; the server
                    does not enforce the documented 1-10 range.
                  value:
                    added:
                      movies: 0
                      shows: 0
                      statuses: []
                    not_found:
                      movies:
                        - rating: 100
                          ids:
                            simkl: 752138
                          type: movie
                      shows: []
                quirk_out_of_range_rating_11:
                  summary: 'Quirk: server accepts rating 11 (out of 1-10 range)'
                  description: >-
                    The server returned status 201 for `rating: 11`. Stored
                    value lookup: None. Validate ratings client-side; the server
                    does not enforce the documented 1-10 range.
                  value:
                    added:
                      movies: 0
                      shows: 0
                      statuses: []
                    not_found:
                      movies:
                        - rating: 11
                          ids:
                            simkl: 752138
                          type: movie
                      shows: []
                rate_anime_8:
                  summary: Rate an anime 8/10
                  description: >-
                    Anime use the `anime` array key in the REQUEST. (Note: in
                    the /sync/ratings/{type}/{rating} READ response, anime are
                    wrapped in `show:` for cross-catalog compatibility — see
                    /conventions/null-values.)
                  value:
                    added:
                      movies: 0
                      shows: 1
                      statuses:
                        - request:
                            rating: 8
                            ids:
                              simkl: 831411
                              mal: 38000
                              anidb: 14116
                              anilist: 101922
                            type: show
                          response:
                            status: watching
                    not_found:
                      movies: []
                      shows: []
                rate_movie_1:
                  summary: Rate a movie 1/10
                  description: >-
                    Standard rating shape. The server stores the value verbatim
                    (no rounding). Re-POSTing with a different rating
                    overwrites; there's no need to call /sync/ratings/remove
                    first.
                  value:
                    added:
                      movies: 1
                      shows: 0
                      statuses:
                        - request:
                            rating: 1
                            ids:
                              simkl: 752138
                            type: movie
                          response:
                            status: completed
                    not_found:
                      movies: []
                      shows: []
                rate_movie_10:
                  summary: Rate a movie 10/10
                  description: >-
                    Standard rating shape. The server stores the value verbatim
                    (no rounding). Re-POSTing with a different rating
                    overwrites; there's no need to call /sync/ratings/remove
                    first.
                  value:
                    added:
                      movies: 1
                      shows: 0
                      statuses:
                        - request:
                            rating: 10
                            ids:
                              simkl: 752138
                            type: movie
                          response:
                            status: completed
                    not_found:
                      movies: []
                      shows: []
                rate_movie_5:
                  summary: Rate a movie 5/10
                  description: >-
                    Standard rating shape. The server stores the value verbatim
                    (no rounding). Re-POSTing with a different rating
                    overwrites; there's no need to call /sync/ratings/remove
                    first.
                  value:
                    added:
                      movies: 1
                      shows: 0
                      statuses:
                        - request:
                            rating: 5
                            ids:
                              simkl: 752138
                            type: movie
                          response:
                            status: completed
                    not_found:
                      movies: []
                      shows: []
                rate_movie_8:
                  summary: Rate a movie 8/10
                  description: >-
                    Standard rating shape. The server stores the value verbatim
                    (no rounding). Re-POSTing with a different rating
                    overwrites; there's no need to call /sync/ratings/remove
                    first.
                  value:
                    added:
                      movies: 1
                      shows: 0
                      statuses:
                        - request:
                            rating: 8
                            ids:
                              simkl: 752138
                            type: movie
                          response:
                            status: completed
                    not_found:
                      movies: []
                      shows: []
                rate_show_9:
                  summary: Rate a TV show 9/10
                  description: Same shape as movies; just swap the array key to `shows`.
                  value:
                    added:
                      movies: 0
                      shows: 1
                      statuses:
                        - request:
                            rating: 9
                            ids:
                              simkl: 2090
                            type: show
                          response:
                            status: watching
                    not_found:
                      movies: []
                      shows: []
        '400':
          $ref: '#/components/responses/BadRequest'
        '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:
    RatingsAddRequest:
      type: object
      description: >-
        Request body for adding/updating ratings. Each item must include
        `rating` (1–10 integer). To unset a rating without re-rating, use [`POST
        /sync/ratings/remove`](/api-reference/simkl/remove-ratings) instead.
        Items go under `movies[]`, `shows[]`, or `anime[]` — Simkl resolves
        anime titles correctly under either `shows[]` or `anime[]`, so match the
        field to your data type when known.
      properties:
        movies:
          type: array
          items:
            $ref: '#/components/schemas/RatingItem'
        shows:
          type: array
          items:
            $ref: '#/components/schemas/RatingItem'
        anime:
          type: array
          items:
            $ref: '#/components/schemas/RatingItem'
          description: Array of anime entries (same shape as `shows[]`).
    RatingsAddResponse:
      type: object
      description: Response from `POST /sync/ratings`.
      properties:
        added:
          type: object
          properties:
            movies:
              type: integer
            shows:
              type: integer
            statuses:
              type: array
              items:
                $ref: '#/components/schemas/SyncStatusItem'
        not_found:
          $ref: '#/components/schemas/NotFoundReport'
    RatingItem:
      type: object
      required:
        - rating
        - ids
      properties:
        rating:
          type: integer
          minimum: 1
          maximum: 10
        rated_at:
          type: string
          format: date-time
        ids:
          $ref: '#/components/schemas/Ids'
    SyncStatusItem:
      type: object
      description: Per-item result of a sync write.
      properties:
        request:
          type: object
          description: Echo of the input item.
        response:
          type: object
          properties:
            status:
              type: string
              enum:
                - watching
                - plantowatch
                - hold
                - dropped
                - completed
                - removed
              description: Resulting list status.
            simkl_type:
              type: string
              enum:
                - movie
                - tv
                - anime
            anime_type:
              type:
                - string
                - 'null'
              description: >-
                Type 4 null — data not on file in that field's slot. See [Null
                and missing values](/conventions/null-values).
        rewatch_id:
          type: integer
          description: >-
            Present when a rewatch session was created or resumed by this call.
            Use this value on subsequent writes to update the same session.
        rewatch_status:
          type: string
          enum:
            - active
            - completed
            - closed
          description: >-
            Lifecycle state of the rewatch session referenced by `rewatch_id`.
            Writable values: `active`, `closed`, and (movies only) `completed`.
            For TV / anime, `completed` is silently clamped to `closed` — only
            coverage of every aired regular episode (specials in season 0 don't
            count) promotes a show session to `completed`. A `closed` write
            against a coverage-earned `completed` session is a no-op. Resuming a
            finished session without `rewatch_id` requires a `last_watched_at` /
            `watched_at` that pinpoints the target row; bare `is_rewatch: true`
            (no `rewatch_status`) always targets the singleton `active` session.
            See [Rewatches guide → Session
            states](/guides/rewatches#session-states).
    NotFoundReport:
      type: object
      description: >-
        Items Simkl could not match. Inspect this to surface diagnostics back to
        your users. Anime entries that fail to resolve land in `shows`
        regardless of which top-level array they were sent under — there is no
        separate `anime` bucket in not_found.
      properties:
        movies:
          type: array
          items:
            $ref: '#/components/schemas/Movie'
        shows:
          type: array
          items:
            $ref: '#/components/schemas/Show'
        episodes:
          type: array
          items:
            $ref: '#/components/schemas/Show'
    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
    Movie:
      type: object
      description: >-
        Standard movie object. See the [Standard Media
        Objects](/conventions/standard-media-objects) guide.
      properties:
        title:
          type: string
          example: 'Terminator 3: Rise of the Machines'
        year:
          type: integer
          example: 2003
        ids:
          $ref: '#/components/schemas/Ids'
      required:
        - ids
    Show:
      type: object
      description: >-
        Standard show object. May include nested seasons/episodes for partial
        sync.
      properties:
        title:
          type: string
          example: The Walking Dead
        year:
          type: integer
          example: 2010
        ids:
          $ref: '#/components/schemas/Ids'
        seasons:
          type: array
          items:
            type: object
            properties:
              number:
                type: integer
              episodes:
                type: array
                items:
                  $ref: '#/components/schemas/Episode'
      required:
        - ids
    Episode:
      type: object
      description: Episode reference. Use `season` + `number`, or `ids`.
      properties:
        season:
          type: integer
          minimum: 0
          example: 1
        number:
          type: integer
          minimum: 1
          example: 2
        watched_at:
          type: string
          format: date-time
          example: '2014-09-01T09:10:11Z'
          description: ISO-8601 GMT timestamp.
        ids:
          $ref: '#/components/schemas/Ids'
  responses:
    BadRequest:
      description: Bad request — a required field is missing or has the wrong shape.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: empty_field
            code: 400
            message: Missed "to" parameter
    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

````