Skip to main content
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.
Two watches of the same movie or episode must be at least 2 days apart. Anything closer is absorbed into the existing session rather than starting a new one — it’s a rewatch, not a rewind. On POST /scrobble/stop you’ll see rewatch_status: "too_soon"; on POST /sync/history the write is simply a no-op.This is deliberate: it absorbs player retries, scrubbing back and forth across the 80% mark, and importer re-runs, which would otherwise fill the user’s history with phantom sessions. Don’t treat it as an error or retry it.Details and the other ceilings (50 sessions per item) in The 2-day gap and Limits and rules.
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, importer re-runs, or retries. Player scrobbling is supported on POST /scrobble/stop and POST /scrobble/checkin, and only when the user asked for it: sending the flag on /scrobble/start can silently open a session on whatever the user previously left playing. See Scrobble.
  3. Default off in your app settings. Expose a per-user “Track rewatches” toggle, starting disabled — many users prefer plain “mark watched” without session bookkeeping. Check the user’s plan when they switch it on and prompt for an upgrade if they’re on the free tier: Adding a rewatch toggle to your app.
  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 on POST /sync/history — the request still consumes a rate-limit slot but returns added: { movies: 0, shows: 0, episodes: 0 }. POST /scrobble/stop is the exception: it answers rewatch_status: "pro_required", so you can read the reason off the response. Either way, gate the flag behind the user’s plan instead of sending it blindly:
  1. On sign-in (or first launch), call GET /users/settings once and read account.type — one of "free", "pro", or "vip". Cache it locally.
  2. Don’t refetch on every poll. User settings are set-and-forget. Watch activities.settings.all from GET /sync/activities (you’re already polling it for the sync delta — see the Sync guide); when that timestamp bumps, refetch /users/settings once to pick up plan upgrades, downgrades, timezone changes, etc.
  3. If account.type === "free", hide or disable the “Rewatch” button with a short note like “Rewatch tracking requires Simkl PRO or VIP” and link to simkl.com/vip. Don’t send ?allow_rewatch=yes for free-tier users — there’s no value in firing a request that the server will silently drop.

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.

POST /scrobble/stop

For players. ?allow_rewatch=yes on a stop with progress ≥ 80, or on a check-in. Never on /scrobble/start.

Adding a rewatch toggle to your app

Rewatch tracking is opt-in twice over: once by the user in your settings, and once by Simkl’s plan gate. Wire both before you send ?allow_rewatch=yes anywhere.

1. Ship it off by default

The toggle must start disabled. Most people just want “mark watched” and would be confused by a title that shows as completed and has a session in progress. Turning it on should be a deliberate act. Something like this in your settings screen:

2. Check the plan when they flip it on

Rewatches are a Simkl PRO / VIP feature. Check before enabling, not after the first failed write — a free-tier POST /sync/history with the flag is a silent no-op that still costs a rate-limit slot. You should already have account.type cached from sign-in (see the plan check). If it isn’t "pro" or "vip", don’t enable the toggle — show an upgrade prompt instead:
Point Upgrade at https://simkl.com/vip/.
Don’t call GET /users/settings every time the user opens your settings screen. Read the cached value. It refreshes on its own when activities.settings.all from POST /sync/activities changes — the same poll you already run for the sync delta.If you want to be certain the plan is current at the exact moment someone taps Upgrade and comes back, refetch once on app foreground, not on every render.

3. Handle the downgrade

A user can be PRO today and free next month. When activities.settings.all bumps and the refetched account.type comes back "free", stop sending the flag and disable the toggle — but don’t delete anything. Their existing sessions stay on their Simkl account and reappear if they resubscribe. Both surfaces fail safely if you miss it: POST /sync/history no-ops, and POST /scrobble/stop answers rewatch_status: "pro_required", which is a good trigger to flip your cached flag and re-check the plan.

Quick start

A movie rewatch:
A TV episode rewatch — first call creates the session:
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.

Scrobble (players and media servers)

If your app is a player that reports playback with /scrobble/* instead of writing history directly, you can record rewatches too. The problem it solves: your user finishes Interstellar for the third time. Your player sends /scrobble/stop at 95%. Simkl looks at it, sees the movie is already marked watched, and throws the event away. The user’s rewatch count never moves. The fix: add ?allow_rewatch=yes to that one call. Simkl then logs it as a separate viewing session instead of discarding it.

The whole feature in one call

progress must be ≥ 80. That’s the threshold at which a stop marks the item watched, and a rewatch can only exist where a watch does. A stop below 80 resolves to action: "pause" — it saves a resume position, marks nothing, and returns no rewatch fields at all, whether or not you sent the flag.If you’re getting no rewatch_status back, check action in the response first. "pause" means the progress was too low, not that the rewatch failed.
You get the normal scrobble response plus two extra keys:

Reading the result

rewatch_status is the only field you need to branch on. Three values mean it worked: The rest tell you why nothing was saved. None of them are errors you need to retry:
rewatch_id is not a success signal. A session that’s already running reports its id even when it rejected your watch — inside the 2-day gap, for example. If you check if (rewatch_id) you will tell users a rewatch was saved when it wasn’t. Always branch on rewatch_status.
This list is longer than the rewatch_status on GET /sync/all-items, which only ever returns the three session states. If you share an enum type between the two, widen it here.

Send it on stop or checkin, never on start or pause

Never put allow_rewatch=yes on /scrobble/start. It will eventually log a rewatch on a title the user isn’t even watching.Here’s how: your user watched 90% of Dune last night, then closed the player without a clean stop. Tonight they press play on Arrival. Your /scrobble/start for Arrival makes Simkl tidy up that stale Dune session first — and if the flag is on the URL, Dune gets logged as a rewatch. Nothing in the response mentions it, so you’d never notice in testing.

Check-ins rewatch too, just not straight away

/scrobble/checkin is fire-and-forget: it doesn’t mark anything watched when you call it, so it can’t tell you a rewatch result either. Simkl stores the opt-in on the check-in and applies it when the check-in matures into a watch at the end of the item’s runtime. Three things follow from that, and they’re easy to get wrong:
  • No rewatch_id / rewatch_status in the response. Nothing has happened yet. If you need to confirm the session exists, read it back later from GET /sync/all-items?allow_rewatch=yes.
  • The PRO / VIP check runs at check-in time. A user who is PRO when you check in still gets the rewatch when it matures, even if their plan lapses in between.
  • The decision is frozen once the check-in is recorded. You can’t add or remove the flag afterwards. Deleting the playback before it matures cancels the whole check-in, rewatch included.
If your app can reliably catch a stop event, prefer /scrobble/stop for rewatches — you get an immediate, readable answer instead of having to poll for one.

Wiring it into your player

Four gates, all of which must be true before the flag goes on the URL:

Is the account PRO or VIP?

Check account.type once at sign-in and cache it. See the plan check above.

Has the user enabled rewatch tracking in your settings?

Default this off. Plenty of people just want “watched” without session bookkeeping.

Did the user actually ask for this watch to be a rewatch?

A “Watching again” button, or a prompt when they replay something already completed. Never infer it.

Is this the stop call?

Only /scrobble/stop, with progress ≥ 80 so it actually marks watched.
Free accounts get rewatch_status: "pro_required" back, which is more helpful than POST /sync/history, where a Free-tier rewatch is a silent no-op. Still check the plan client-side so you’re not spending a rate-limit slot to be told no.

Per-item rewatch fields

All fields below are optional. They only take effect when ?allow_rewatch=yes is on the URL.

Session states

A session is in exactly one of three states. For TV / anime, completed is server-earned only: the server promotes a session when the rewatch covers every aired regular episode (specials in season 0 don’t count). For movies, completed is the default and only meaningful state. Transitions you can drive from a write (POST /sync/history?allow_rewatch=yes + rewatch_status: "..."):
  • "active" — start or reactivate a session. The previously-active session for the item auto-closes.
  • "closed" — end the active session. No-op against a completed session; reactivate it first if you really need to close it.
  • "completed" — honored for movies. For TV / anime, silently clamped to "closed" because only coverage earns DONE. Subsequent episode writes that complete coverage will promote the session back to "completed".
Transitions the server runs automatically: any non-completed session (whether active or closed) auto-promotes to completed the moment an episode write pushes coverage to every aired regular episode.

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):
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

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.
A few things to notice in the example above:
  • Same simkl ID on both rows — 17465 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 the session is mid-coverage (4/73). It auto-flips to "completed" when watched_episodes_count >= total_episodes_count - not_aired_episodes_count. See Session states for the full rules.
  • 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

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.
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:
  • Reactivate an older session — rewatch_id: N, rewatch_status: "active". The previously-active session for the 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; resumable by another write to the same rewatch_id. No-op on a completed session — reactivate first.
Pin rewatch_id on every write after the first. Without it, finished sessions need last_watched_at to pinpoint which row to resume — otherwise the server creates a new row. (The singleton active session is always resumable by bare is_rewatch: true.)
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 code is surfaced. To prevent this in your app, check account.type from GET /users/settings at sign-in (cache it locally; refetch only when activities.settings.all bumps — see the PRO / VIP gate at the top of this page). Reading the added counts in the response is a fallback if you can’t gate up-front, but it’s wasteful — the request still consumed a rate-limit slot.
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.