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

# Get a user's watch statistics

> <Warning>
**The most expensive call in the Simkl API. Only fire it on an explicit user action.**

Stats are computed **live on every request** — there is no edge cache and no precomputed result cache. The server walks the user's entire watch history across all three catalogs (movies, TV, anime), looks up the runtime of every completed episode and movie, and aggregates everything from scratch. Response time scales with the size of the user's library.

**OK to call:** when the user opens a "My stats" / "Year in review" / profile screen, or taps a refresh button on a stats widget.

**Do not call:** on app launch, on resume from background, in any polling loop, speculatively to "warm" data, or for every user in a list (e.g. a friends leaderboard — batch via lazy loading). Apps that hammer this endpoint risk rate-limit throttling on the `client_id`.
</Warning>

Returns aggregate stats for the given user — total movies / shows / anime watched, total time spent, episode counts, last-week activity, and basic profile info.

The `user_id` must be a **positive integer** — the numeric Simkl id of the target account. To fetch stats for the **authenticated user**, call [`POST /users/settings`](/api-reference/simkl/get-user-settings) once at app start and cache `account.id`, then pass that value here.

Public profiles can be fetched without a bearer token (clientId-only). Private profiles require either a bearer token belonging to the target user, or a connection the target user has granted the requester (otherwise `403 private_profile`).

This is `POST` for historical reasons — there is no request body.

#### Response shape

```json
{
 "user": {
 "id": 51,
 "name": "username",
 "joined_at": "2018-01-15T00:00:00Z",
 "avatar": "https://simkl.in/avatars/.../user_100.jpg",
 "gender": "Male",
 "loc": "Spain",
 "age": 28,
 "type": "vip"
 },
 "total_mins": 78230,
 "movies": {
 "total_mins": 18000,
 "plantowatch": { "mins": 0, "count": 12 },
 "completed": { "mins": 18000, "count": 200 },
 "dropped": { "mins": 0, "count": 1 }
 },
 "tv": {
 "total_mins": 35000,
 "watching": { "watched_episodes_count": 23, "count": 4, "left_to_watch_episodes": 12, "left_to_watch_mins": 600, "total_episodes_count": 35 }
 },
 "anime": {... },
 "watched_last_week": { "total_mins": 320, "movies_mins": 60, "tv_mins": 200, "anime_mins": 60 }
}
```

The `user` block is omitted when the target user has not loaded any data (e.g. brand-new accounts); only `total_mins` and the per-domain blocks are guaranteed.

#### Errors

| Code | When |
|---|---|
| `404 user_id_failed` | `user_id` is `0` or any non-positive integer. There is **no shortcut** for the authenticated user — always pass a real numeric id. |
| `403 private_profile` | The target user's profile is private and the requester does not have access. |



## OpenAPI

````yaml /openapi.json post /users/{user_id}/stats
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:
  /users/{user_id}/stats:
    post:
      tags:
        - Users
      summary: Get a user's watch statistics
      description: >-
        <Warning>

        **The most expensive call in the Simkl API. Only fire it on an explicit
        user action.**


        Stats are computed **live on every request** — there is no edge cache
        and no precomputed result cache. The server walks the user's entire
        watch history across all three catalogs (movies, TV, anime), looks up
        the runtime of every completed episode and movie, and aggregates
        everything from scratch. Response time scales with the size of the
        user's library.


        **OK to call:** when the user opens a "My stats" / "Year in review" /
        profile screen, or taps a refresh button on a stats widget.


        **Do not call:** on app launch, on resume from background, in any
        polling loop, speculatively to "warm" data, or for every user in a list
        (e.g. a friends leaderboard — batch via lazy loading). Apps that hammer
        this endpoint risk rate-limit throttling on the `client_id`.

        </Warning>


        Returns aggregate stats for the given user — total movies / shows /
        anime watched, total time spent, episode counts, last-week activity, and
        basic profile info.


        The `user_id` must be a **positive integer** — the numeric Simkl id of
        the target account. To fetch stats for the **authenticated user**, call
        [`POST /users/settings`](/api-reference/simkl/get-user-settings) once at
        app start and cache `account.id`, then pass that value here.


        Public profiles can be fetched without a bearer token (clientId-only).
        Private profiles require either a bearer token belonging to the target
        user, or a connection the target user has granted the requester
        (otherwise `403 private_profile`).


        This is `POST` for historical reasons — there is no request body.


        #### Response shape


        ```json

        {
         "user": {
         "id": 51,
         "name": "username",
         "joined_at": "2018-01-15T00:00:00Z",
         "avatar": "https://simkl.in/avatars/.../user_100.jpg",
         "gender": "Male",
         "loc": "Spain",
         "age": 28,
         "type": "vip"
         },
         "total_mins": 78230,
         "movies": {
         "total_mins": 18000,
         "plantowatch": { "mins": 0, "count": 12 },
         "completed": { "mins": 18000, "count": 200 },
         "dropped": { "mins": 0, "count": 1 }
         },
         "tv": {
         "total_mins": 35000,
         "watching": { "watched_episodes_count": 23, "count": 4, "left_to_watch_episodes": 12, "left_to_watch_mins": 600, "total_episodes_count": 35 }
         },
         "anime": {... },
         "watched_last_week": { "total_mins": 320, "movies_mins": 60, "tv_mins": 200, "anime_mins": 60 }
        }

        ```


        The `user` block is omitted when the target user has not loaded any data
        (e.g. brand-new accounts); only `total_mins` and the per-domain blocks
        are guaranteed.


        #### Errors


        | Code | When |

        |---|---|

        | `404 user_id_failed` | `user_id` is `0` or any non-positive integer.
        There is **no shortcut** for the authenticated user — always pass a real
        numeric id. |

        | `403 private_profile` | The target user's profile is private and the
        requester does not have access. |
      operationId: post-users-user-id-stats
      parameters:
        - name: user_id
          in: path
          description: >-
            Target user's numeric Simkl id (positive integer). For the
            authenticated user, first call [`POST
            /users/settings`](/api-reference/simkl/get-user-settings) and pass
            back `account.id`. Passing `0` returns `404 user_id_failed`.
          required: true
          schema:
            type: integer
            minimum: 1
          example: '51'
        - $ref: '#/components/parameters/ClientIdQuery'
        - $ref: '#/components/parameters/AppNameQuery'
        - $ref: '#/components/parameters/AppVersionQuery'
        - $ref: '#/components/parameters/UserAgentHeader'
      requestBody:
        required: false
        description: This endpoint takes no body — it's `POST` for historical reasons.
        content:
          application/json:
            example: {}
      responses:
        '200':
          description: OK
          headers: {}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserStats'
              example:
                total_mins: 908554
                movies:
                  total_mins: 171969
                  plantowatch:
                    mins: 18938
                    count: 173
                  completed:
                    mins: 171852
                    count: 1558
                tv:
                  total_mins: 242560
                  watching:
                    watched_episodes_count: 2612
                    count: 41
                    left_to_watch_episodes: 10276
                    left_to_watch_mins: 411040
                    total_episodes_count: 12888
                  hold:
                    watched_episodes_count: 1157
                    count: 41
                    left_to_watch_episodes: 828
                    left_to_watch_mins: 33120
                    total_episodes_count: 1985
                  plantowatch:
                    watched_episodes_count: 243
                    count: 118
                    left_to_watch_episodes: 12961
                    left_to_watch_mins: 518440
                    total_episodes_count: 13204
                  completed:
                    watched_episodes_count: 2111
                    count: 55
                anime:
                  total_mins: 494025
                  watching:
                    watched_episodes_count: 1747
                    count: 21
                    left_to_watch_episodes: 410
                    left_to_watch_mins: 10250
                    total_episodes_count: 2157
                  hold:
                    watched_episodes_count: 97
                    count: 23
                    left_to_watch_episodes: 462
                    left_to_watch_mins: 11550
                    total_episodes_count: 559
                  plantowatch:
                    watched_episodes_count: 2324
                    count: 719
                    left_to_watch_episodes: 7829
                    left_to_watch_mins: 195725
                    total_episodes_count: 10153
                  completed:
                    watched_episodes_count: 17255
                    count: 1291
                watched_last_week:
                  total_mins: 130
                  movies_mins: 130
                  tv_mins: 0
                  anime_mins: 0
        '403':
          description: >-
            The target user's profile is private and the requester does not have
            access.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: private_profile
                code: 403
        '404':
          description: >-
            `user_id` is `0` or any non-positive integer. There is no shortcut
            for the authenticated user — always pass the real numeric id from
            `account.id` (returned by `POST /users/settings`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: user_id_failed
                code: 404
        '412':
          $ref: '#/components/responses/ClientIdFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      security:
        - clientId: []
        - simklApiKey: []
        - 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:
    UserStats:
      type: object
      description: Aggregate watch statistics for a user.
      properties:
        user:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            joined_at:
              type: string
              format: date-time
            avatar:
              type: string
              format: uri
            gender:
              type: string
            loc:
              type:
                - string
                - 'null'
              description: >-
                Type 4 null — data not on file in that field's slot. See [Null
                and missing values](/conventions/null-values).
            age:
              type: string
              description: >-
                Age in years, pre-formatted as a string like `"30 years"`. Empty
                string if the user has not set their birthday or has disabled
                age display. Pre-formatted by the server — clients should
                display verbatim rather than parsing.
              example: 30 years
            type:
              $ref: '#/components/schemas/AccountPlan'
        total_mins:
          type: integer
          description: Total minutes watched across movies, TV, and anime.
        movies:
          type: object
        tv:
          type: object
        anime:
          type: object
        watched_last_week:
          type: object
    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
    AccountPlan:
      type: string
      enum:
        - free
        - pro
        - vip
      description: >-
        Simkl account plan tier. `free` is the default; `pro` and `vip` are paid
        tiers that unlock higher rate limits and additional features. Some
        endpoints gate behavior on this value.
  responses:
    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

````