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.

A rewatch is a separate viewing session for an item the user already completed. Each session has its own start date, episode progress (for shows / anime), and status (active, completed, or closed). Up to 50 sessions per item. Simkl returns the full session list so you can replicate the simkl.com “Rewatches” panel.
Read this guide before enabling ?allow_rewatch=yes in production. Wired up carelessly — on retries, scrobble events, importer re-runs, or without pinning rewatch_id after the first write — the flag pollutes the user’s history stats and rewatches panel with phantom sessions. Before flipping it on:
  1. Pin rewatch_id on every write after the first. Without it, sessions can fork silently.
  2. Gate ?allow_rewatch=yes behind explicit user intent — a dedicated “Rewatch” button. Never on background syncs, scrobble auto-events, importer re-runs, or retries.
  3. Default off in your app settings. Expose a per-user “Track rewatches” toggle — many users prefer plain “mark watched” without session bookkeeping.
  4. Test the full lifecycle (start → episodes → close → reactivate → complete) on a real account before shipping.
If you just need to mark watched, plain POST /sync/history is what you want — see Mark as watched.
Simkl Pro / VIP only. Free-tier callers with ?allow_rewatch=yes get a silent no-op. If your UI exposes a “Rewatch” button to a free-tier user, disable it with a short note like “Rewatch tracking requires Simkl Pro or VIP” and link to simkl.com/vip.

Endpoints used

POST /sync/history

Write watch events. ?allow_rewatch=yes makes the write land in a rewatch session instead of the canonical row.

GET /sync/all-items

Read sessions back. ?allow_rewatch=yes adds one extra row per saved session alongside the canonical entry.

Quick start

A movie rewatch:
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": { "simkl": 53536 }, "watched_at": "2026-05-10T20:00:00Z" }] }'
A TV episode rewatch — first call creates the session:
POST /sync/history?allow_rewatch=yes

{ "shows": [{ "ids": { "simkl": 1234 }, "seasons": [{ "number": 1, "episodes": [{ "number": 1, "watched_at": "2026-05-10T20:00:00Z" }] }] }] }
Response echoes rewatch_id and rewatch_status inside added.statuses[].response. Cache the rewatch_id and pin it on every subsequent write for that session.

When does a write become a rewatch?

The server always tries the regular write first. If the regular write would actually mark new data, that takes precedence — no rewatch is created. A write lands in a rewatch session when any of these holds:
  1. The item is already Completed and the regular add is a no-op (movies: user already finished it; shows: payload has no episodes and the show is Completed).
  2. You included episodes and every one is already on the user’s history — i.e. the regular write would change nothing.
  3. You passed is_rewatch: true on the item.
is_rewatch: true does not bypass the regular-first logic. If the write tries to add a date that already exists in canonical history, it’s still rejected. The flag’s main use is to create an empty rewatch session on a Completed item your UI explicitly marked as “starting a rewatch” — without writing any episodes yet. The episode or movie must already exist on the user’s history (completed / watching / hold / dropped) — Plan-to-Watch entries can’t be rewatched, there’s nothing to rewatch.

Per-item rewatch fields

All fields below are optional. They only take effect when ?allow_rewatch=yes is on the URL.
FieldTypePurpose
is_rewatchboolTrigger rewatch logic explicitly. See When does a write become a rewatch? for what overrides what.
rewatch_idintResume a specific session. Use the value from a previous response. Without it, the server picks the item’s active session if one exists, else creates a new session.
rewatch_statusstringSet session state directly: active, completed, or closed. Use to explicitly close, reactivate (the simkl.com “Reactivate” gesture), or force-complete.
watched_atstring (ISO-8601)Standard per-item timestamp. Also serves as the session’s start date.
last_watched_atstring (ISO-8601)Primarily a response field from GET /sync/all-items (the session’s most recent watch event). Rarely set on writes.

Session states

active ──── auto: all aired eps watched ────► completed

   └──── manual: rewatch_status="closed" ────► closed
                  (with some episodes watched)
StateMeaningHow it’s set
activeIn progress. Only one per item at a time.Default for new TV / anime sessions; or rewatch_status: "active" to reactivate a closed / completed session.
completedDone — every aired episode rewatched.Auto when watched_episodes_count >= aired_episode_count. Default for new movie sessions (no episodes to track). Or rewatch_status: "completed".
closedThe user closed the session. Resumable at any time.rewatch_status: "closed". Also set automatically on the previously-active session when another session is made active for the same item. Can be set on any session — including one with zero episodes watched (the simkl.com UI then shows it as completed, with no “partial” label).
The flow is: a new session starts as active, then becomes either completed (all aired episodes watched) or closed (user ended the session before reaching the end). Manual transitions via rewatch_status can move a session between any states — that’s how simkl.com’s “Reactivate this rewatch” gesture works.

The 2-day gap

Any two watch events on the same item (movie or individual episode) must be at least 2 days apart, or the second write collapses into the first session. It’s a rewatch, not a rewind 😄. The gap is per-item, not per-show. Watching S1E1 on Monday and S1E2 on Tuesday is fine — those are different items. The rule only fires when the same episode is written twice within 48h.
Why a 2-day gap? It absorbs common timestamping problems that would otherwise inflate the user’s history with phantom rewatches:
  • Sleep-and-resume — user starts an episode at night, finishes the next morning. One viewing, not two.
  • Wrong timezones — clients labeling local time as UTC (or vice versa) drift 1–12 hours per write.
  • DST transitions — naive date libraries miscalculate by an hour twice a year.
  • Multi-device duplicates — phone + TV both report the same play.
  • Retry storms — flaky connections retry a write that already landed.
  • Importer re-runs — re-running an import re-sends the same watched_at.
  • Scrobble pause/resume noise — bathroom break → re-fire of /start + /stop.
  • Buggy progress reporting — players hitting 80%+ multiple times on seeks.
Real rewatches happen days, weeks, or months apart — not within hours. The 48-hour rule encodes that.

Reading sessions back

?allow_rewatch=yes on GET /sync/all-items adds one extra entry per saved rewatch session alongside the canonical entry for each item. Always pair with date_from — never call without it outside the initial Phase-1 sync.
allow_rewatch=yes alone returns summary-only rewatch rows. watched_episodes_count returns 0 as a sentinel, last_watched is null, and seasons[].episodes[] is absent — even when the session actually has episodes recorded. To get per-episode data on rewatch rows you must add extended=full (and usually episode_watched_at=yes for timestamps):
GET /sync/all-items/all/completed?allow_rewatch=yes&extended=full&episode_watched_at=yes
Apps that mirror simkl.com’s “Rewatches” panel — or that need to apply per-episode diffs from rewatch sessions to a local cache — should always use this flag combo. The Flag combinations table below has the full set.

Canonical vs rewatch rows

FieldCanonical (is_rewatch: false)Rewatch (is_rewatch: true)
is_rewatchfalse (emitted when ?allow_rewatch=yes is set; otherwise absent)true
rewatch_idabsentsession id
rewatch_statusabsentactive / completed / closed
last_watched_atThe canonical (original) last watch date for the item — does not reflect rewatch sessions.This session’s most-recent watch event.
watched_episodes_countEpisodes marked watched on the canonical row (e.g. 62/62 for a finished show). Not a lifetime total across rewatches.Per-session count — 0 as a sentinel without extended=full.
The canonical row and the rewatch rows are independent: last_watched_at and watched_episodes_count on the canonical entry never incorporate rewatch progress, no matter how many sessions exist for the item.

Worked example — Game of Thrones with one rewatch session

A representative shape from GET /sync/all-items?allow_rewatch=yes&extended=full&episode_watched_at=yes — Game of Thrones (Simkl ID 17465) finished long ago, currently being rewatched in progress (4 of 73 episodes into S1). The item appears twice in shows[]: once as the canonical row, once as the in-progress rewatch row.
{
  "shows": [
    {
      "added_to_watchlist_at":   "2026-05-15T00:13:18Z",
      "last_watched_at":         "2026-05-15T00:13:18Z",
      "user_rated_at":           "2026-05-16T14:20:16Z",
      "user_rating":             10,
      "status":                  "completed",
      "last_watched":            null,
      "next_to_watch":           "S01E01",
      "watched_episodes_count":  73,
      "total_episodes_count":    73,
      "not_aired_episodes_count": 0,
      "show": {
        "title":  "Game of Thrones",
        "poster": "57/5742576cd8f59fcb0",
        "year":   2011,
        "runtime": 52,
        "ids": {
          "simkl":        17465,
          "slug":         "game-of-thrones",
          "imdb":         "tt0944947",
          "tvdbslug":     "game-of-thrones",
          "trakttvslug":  "game-of-thrones",
          "tvdb":         "121361",
          "tmdb":         "1399"
        }
      },
      "is_rewatch": false
    },
    {
      "added_to_watchlist_at":   "2026-05-16T14:02:10Z",
      "last_watched_at":         "2026-05-16T20:00:00Z",
      "user_rated_at":           "2026-05-16T14:20:16Z",
      "user_rating":             10,
      "status":                  "completed",
      "last_watched":            "S01E04",
      "next_to_watch":           "S01E05",
      "watched_episodes_count":  4,
      "total_episodes_count":    73,
      "not_aired_episodes_count": 0,
      "show": { "title": "Game of Thrones", "ids": { "simkl": 17465, "slug": "game-of-thrones" /* ... */ } },
      "is_rewatch":      true,
      "rewatch_id":      7482,
      "rewatch_status":  "active",
      "seasons": [
        {
          "number": 1,
          "episodes": [
            { "number": 1, "watched_at": "2026-05-16T14:02:10Z" },
            { "number": 2, "watched_at": "2026-05-16T14:02:10Z" },
            { "number": 3, "watched_at": "2026-05-16T19:00:00Z" },
            { "number": 4, "watched_at": "2026-05-16T20:00:00Z" }
          ]
        }
      ]
    }
  ]
}
A few things to notice in the example above:
  • Same simkl ID on both rows17465 appears twice. Group by ids.simkl when iterating; treat is_rewatch: true rows as side-cars on the canonical entry.
  • Canonical watched_episodes_count is 73/73 (the user finished the show originally); the rewatch row’s 4/73 is per-session and does NOT advance the canonical count.
  • last_watched_at is independent on each row — canonical reflects the original final watch (2026-05-15); the rewatch row reflects its own most-recent episode (2026-05-16T20:00:00Z).
  • rewatch_status: "active" because 4/73 < total_episodes_count. It would auto-flip to "completed" when watched_episodes_count >= total_episodes_count - not_aired_episodes_count, or you can force it via a rewatch_status write at any time. A session manually overridden to "completed" with fewer episodes watched is legal but represents an explicit user gesture, not natural progression.
  • seasons[].episodes[] only appears on the rewatch row under this flag combo (extended=full + episode_watched_at=yes). Without extended=full, the rewatch row would arrive summary-only: watched_episodes_count: 0 sentinel, no seasons[] block.
  • Movies are simpler — no seasons[], just movie: { ... }, and the rewatch row’s rewatch_status always starts at completed (movies have nothing to track episode-wise).
  • Anime rewatch rows have the same shape as show rows plus an anime_type field ("tv", "movie", etc.) on the rewatch row itself.

Flag combinations for episode lists

Flags (on top of allow_rewatch=yes)What rewatch entries carry
(none)Summary only. watched_episodes_count returns 0 as a sentinel even when the session actually has episodes.
extended=fullSummary fields populated with real per-session counts, plus seasons[].episodes[] listing episode number.
extended=full_anime_seasonsSame plus anime tvdb mapping per episode.
extended=full + episode_watched_at=yesSame plus per-episode watched_at (from each episode’s completed_on).
episode_watched_at=yes is a modifier — it adds timestamps to episodes already loaded. Without extended=full, episodes aren’t loaded and the flag has no effect on rewatch entries.

UI patterns (mirror simkl.com)

Filter ?allow_rewatch=yes response for the current item and count by rewatch_status. closed is the “partial” count; completed is the clean-finish count.
const sessions = response.filter(r => r.is_rewatch && r.show.ids.simkl === currentId);
const completed = sessions.filter(s => s.rewatch_status === 'completed').length;
const closed    = sessions.filter(s => s.rewatch_status === 'closed').length;
const lastDate  = sessions.map(s => s.last_watched_at).sort().pop();
Find the active session and show watched_episodes_count / total_episodes_count. Number the badge by chronological index among all sessions for the item — the number isn’t stored on the server, you compute it client-side.Movies don’t really have an active phase — every movie rewatch session flips to completed immediately.
Fetch the active session with ?allow_rewatch=yes&extended=full&date_from=<your-last-sync>, walk total_episodes_count, find the first episode not in the session’s seasons[].episodes[] list, and POST it pinned to the session’s rewatch_id.
All three are POST /sync/history?allow_rewatch=yes with different combinations:
  • Reactivate an older session: rewatch_id: N, rewatch_status: "active". The previously-active session for that item auto-closes.
  • Start new: write the first episode (or is_rewatch: true for an empty session on a Completed item).
  • Close: rewatch_id: N, rewatch_status: "closed". Keeps watched_episodes_count; can be resumed by another write to the same rewatch_id.
Filter rewatch entries, sort by last_watched_at ascending. Label sessions “Rewatch #1”, “Rewatch #2”, etc. on the client.

Limits and rules

  • Maximum 50 rewatches per item (movie, show, or anime — combined cap).
  • One active session per item. Making another session active auto-closes the previous active one.
  • 2-day minimum gap between watch events on the same movie or episode. See The 2-day gap.
  • Simkl Pro / VIP only. Free-tier writes are silent no-ops.

Edge cases

Returns 200 OK with added: { movies: 0, shows: 0, episodes: 0 }. No error. Detect by checking the user’s subscription tier client-side, or by inspecting the added counts in the response.
Subsequent attempts past 50 may start being rejected once server-side enforcement is added. Count sessions client-side before exposing a “Start new rewatch” button.
Starting a new active session auto-closes the previous one (its watched_episodes_count is preserved). The old session is still readable and still counts toward the 50-cap.
A rewatch write — including one with is_rewatch: true — is rejected if the watched_at date matches an existing canonical episode/movie row. Use a different watched_at value, or omit the conflicting episode.
Second write collapses into the same session. watched_episodes_count doesn’t increment; last_watched_at updates to the latest value. Intentional — see The 2-day gap.
If an episode is already in the active session but a new write for that same episode arrives with a watched_at more than 48h away, the server closes the current active session (preserving its watched_episodes_count) and opens a fresh active session with the conflicting episode at the new timestamp. Common with offline-capable apps catching up after a long sync gap.
When a rewatch session is created through the simkl.com web UI’s “Start new rewatch” button, the server bulk-inserts episode rows for every episode the user had previously watched (a snapshot of their prior progress). Sessions created via plain API writes don’t do this. Re-fetch /sync/all-items after a session was created on simkl.com to see what’s actually there.
Intentional “watched, date unknown / long ago” placeholder — not a NULL or bug. See Dates and timezones → “Very long time ago” placeholder.

Reference

Sync guide

Two-phase model, date_from semantics, deletion reconciliation, edge cases.

Mark as watched

Simple “mark watched” flow without rewatch sessions.

POST /sync/history

Endpoint reference + interactive playground.

GET /sync/all-items

Endpoint reference + interactive playground.