Skip to main content

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.

If you just want to record that a user finished something — a movie, an episode, a whole season — use a single POST /sync/history call. You don’t need scrobbling, and you don’t need a media player.
Use scrobble only if you’re tracking real-time playback (a media-server plugin, a video player). If you just want a “Mark as watched” button, this page is what you need.

Endpoints used on this page

POST /sync/history

Mark one or many items as watched. The main endpoint for this guide.

POST /sync/history/remove

Undo a mark-as-watched. Same payload shape.

POST /scrobble/start

For media players: report that playback has begun.

POST /scrobble/stop

For media players: stop playback. ≥80% progress = marked watched.

Mark a movie

curl -X POST "https://api.simkl.com/sync/history?client_id=$CLIENT_ID&app-name=my-app-name&app-version=1.0" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-app-name/1.0" \
  -d '{
    "movies": [
      { "ids": { "imdb": "tt1201607" } }
    ]
  }'
You can pass any ID Simkl recognizes — see Supported ID keys for the full list with types and examples. Full payload reference at POST /sync/history.
User doesn’t remember when they watched it? POST "watched_at": "1970-01-01T00:00:01Z" — Simkl’s “Very long time ago / I don’t remember” placeholder. simkl.com’s “When did you watch this?” date picker exposes this as a smart-suggestion card; clients should mirror that option. Full convention (including detection rule for reading and recommended date-picker UX patterns) at Dates and timezones → “Very long time ago” placeholder.
Send every identifier you have — title, year, and the full ids object.Simkl walks the ids object in priority order (simkl first when present, then external IDs like imdb, tmdb, tvdb, mal, anidb, …). If no ID resolves, it falls back to a title + year match, then to title-only as a last resort. Sending everything you know — title, year, plus every external ID your client has cached — maximizes the chance the right item gets credited. Extra fields are free; missing fields can cause a 404 id_err or, worse, a silent mismatch.You don’t need to search before writing. Endpoints like /scrobble/*, /sync/history, /sync/add-to-list, and /sync/ratings resolve IDs server-side — pass whatever you have directly, no /search/* round-trip required. Calling /search/id first to “resolve” a Simkl ID is wasted work that doubles your API quota for no gain.See Supported ID keys for the full list.

Mark an episode

{
  "shows": [
    {
      "ids": { "tmdb": "1399" },
      "seasons": [
        {
          "number": 1,
          "episodes": [{ "number": 1 }]
        }
      ]
    }
  ]
}

Mark a whole season

Drop the episodes array — Simkl marks every episode in the listed season:
{
  "shows": [
    {
      "ids": { "tmdb": "1399" },
      "seasons": [{ "number": 1 }]
    }
  ]
}

Mark a whole show

Drop both seasons and episodes:
{
  "shows": [
    { "ids": { "tmdb": "1399" } }
  ]
}

Mix everything in one call

Movies, shows, and anime can be sent together. Useful for importing watch history from another tracker. Anime entries are equally valid under either shows[] or anime[] — Simkl resolves the catalog by ids either way (see Anime in shows[] or anime[]).
{
  "movies": [
    { "ids": { "imdb": "tt1201607" } },
    { "ids": { "tmdb": "12445" } }
  ],
  "shows": [
    { "ids": { "tvdb": "121361" } }
  ],
  "anime": [
    { "ids": { "mal": "4246" } }
  ]
}

When to use which endpoint

Mark as watched

POST /sync/history — instant, one call. Best for “Mark watched” buttons, importers, manual logging.

Scrobble

Real-time playback tracking. Best for media players (Plex, Jellyfin, custom apps) that report actual user events.
Scrobble does mark the item as watched, but only at the end of playback — either when you call POST /scrobble/stop with ≥ 80% progress, or when POST /scrobble/checkin auto-completes at 100% based on the item’s runtime. POST /scrobble/start alone does not mark anything watched — it only puts the title in the user’s “Watching now” banner. If you’re already scrobbling, you don’t also need to call /sync/history.

Record a rewatch (Simkl Pro / VIP)

A POST /sync/history call on an already-Completed item is normally a no-op. Pass ?allow_rewatch=yes to record an additional viewing as a separate rewatch session — but read the full Rewatches guide first. The flag opens a parallel write path that, used carelessly, pollutes the user’s history stats and rewatches panel with phantom sessions. The guide covers session lifecycle (active / completed / closed), per-item fields (rewatch_id, is_rewatch, …), episode-level tracking on shows, reading sessions back from GET /sync/all-items, UI patterns simkl.com uses, and — critically — the precautions you have to implement before enabling the flag.
curl -X POST "https://api.simkl.com/sync/history?allow_rewatch=yes&client_id=$CLIENT_ID&app-name=my-app-name&app-version=1.0" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-app-name/1.0" \
  -d '{ "movies": [{ "ids": { "imdb": "tt1201607" }, "watched_at": "2026-05-10T20:00:00Z" }] }'

Removing items

Made a mistake? Remove with the same payload shape, just hit POST /sync/history/remove:
curl -X POST "https://api.simkl.com/sync/history/remove?client_id=$CLIENT_ID&app-name=my-app-name&app-version=1.0" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-app-name/1.0" \
  -d '{ "movies": [{ "ids": { "imdb": "tt1201607" } }] }'
A few POST /sync/history behaviors worth knowing:
  • No seasons / episodes and no status for a show. Simkl picks based on the show’s airing status: finished airing → Completed with every episode marked; currently airing → Watching with all already-aired episodes marked (future episodes left untouched); not yet released → Watching with no episodes marked.
  • Set status without touching episodes. Pass status: "watching" (or any other status) on the show to move the watchlist bucket without auto-marking episodes.
  • Episode-level ids override number. When an episode object includes ids.tvdb or ids.anidb, those IDs take precedence over the number field for matching — useful for absolute-order anime numbering or specials whose episode numbers don’t line up across sources. That said, prefer season + number when you have them — episode IDs can be re-issued when catalogs merge or re-number, while S1E4 is stable forever (see Episode IDs).
  • Skip the auto-fill. Add ?skip_auto_watching=yes to suppress the auto-mark behavior for shows posted without explicit episodes. Only takes effect when the request also sets an explicit status on the show.