Scrobbling is how a media player reports real-time playback to Simkl. As the user starts, pauses, and finishes watching, your app sends events with the currentDocumentation Index
Fetch the complete documentation index at: https://api.simkl.org/llms.txt
Use this file to discover all available pages before exploring further.
progress. Simkl tracks the active session, shows the title in the user’s “Watching now” banner, and marks the item watched once playback completes.
If you only need a “Mark as watched” button without real-time progress reporting, use Mark as watched instead.

The lifecycle at a glance
Four endpoints. The 80% threshold is the only “magic number” you have to remember.| Endpoint | Call when | Effect |
|---|---|---|
start | Playback begins or resumes | Creates an active session and shows the title in the user’s “Watching now” banner. |
pause | User pauses | Saves current progress. The session is resumable from any device on the same account. |
stop | User stops or playback ends | progress ≥ 80 → marked watched. progress < 80 → kept as a saved playback (resumable later). |
checkin | Fire-and-forget alternative | One call, no progress tracking. Server auto-completes shortly after the item’s runtime expiry — typically within ~2 minutes. |
Which pattern do I need?
/scrobble/* periodically. Send events only on real user actions — pressed Play, Paused, Stopped — with the current progress percentage. Simkl automatically advances progress between events using the item’s known runtime, so periodic re-posting wastes API quota and triggers rate limits.Universal player-event mapping
This is the table to keep open while you’re wiring your player. It’s intentionally player-agnostic — the same mapping works for HTML5<video>, AVPlayer, ExoPlayer, libVLC-based players, Kodi, Roku, Stremio addons, and anything else that emits playback events.
| Player event | Scrobble call | When / why |
|---|---|---|
| Playback started (from beginning) | POST /scrobble/start with progress: 0 | First time the user opens the title. |
| Playback resumed (from pause) | POST /scrobble/start with current progress | Replaces the existing session and clears prior pauses. |
| User pauses | POST /scrobble/pause with current progress | Server saves the session for cross-device resume. |
| User stops or closes the player | POST /scrobble/stop with current progress | Below 80 saves a paused session; >= 80 marks watched. |
| Playback ends naturally (credits / next-up) | POST /scrobble/stop with progress: 100 | Server marks the title watched. |
| User seeks (any direction) within the same item | No call | Update your local progress variable. Reported on the next event. |
| User scrubs aggressively | No call | Same rule. The 20-sec lock would reject overlapping calls anyway. |
| User changes subtitles or audio track | (no call) | Doesn’t affect progress. |
| User skips to next episode | POST /scrobble/stop (current item) → POST /scrobble/start (next) | Two calls, both at the right progress. |
| Network error during a call | retry on next event | Don’t loop on retries — the next real event covers it. |
Handling seek and scrub events
When the user drags the playhead — fast-forwarding, rewinding, or scrubbing aggressively — don’t fire a scrobble call for the seek itself. Just update your localprogress variable. The next real player event (play / pause / stop) reports the new position.
Why? Each scrobble call counts against your client’s request budget, and the server’s 20-second per-user lock will reject overlapping in-flight calls. A user who scrubs three times per minute would burn 180 calls/hour for nothing — and miss the actual lifecycle events.
Here’s a typical session with seeks, showing which events fire calls and which don’t:
Progress can be non-monotonic between calls. A user can pause at 80%, rewind, and stop at 30% — the second progress: 30 is correct and your code shouldn’t try to “fix” it. The server stores whatever you send.
The ≥80% auto-scrobble threshold only fires on stop, not pause. A user can scrub past 80% mid-playback dozens of times without triggering the watched mark — it only counts when they actually stop.
start only puts the title in the “Watching now” banner. The item is marked watched only when:- you call
/scrobble/stopwithprogress >= 80, or - you used
/scrobble/checkinand Simkl’s runtime-based progress reaches 100%, or - you separately call
POST /sync/history.
Reference scrobbler implementations
Below is the same minimalScrobbler in four languages. Each one:
- exposes
start(item),pause(progress),stop(progress)andcheckin(item), - attaches the standard auth header and required URL params,
- shows how to wire it to a real player’s events.
movie field with show + episode or anime + episode (see Examples below).
Continue Watching across devices
Pause/stop on one device, resume on another. Any signed-in device with the sameaccess_token can pick up where the user left off. The pattern:
Device A: pause
POST /scrobble/pause with the user’s token and current progress. Simkl saves the position as a playback.Device B: gate the refetch on activities
POST /sync/activities returns a playback timestamp per media-type bucket. Compare it to the value you saved last sync — if it hasn’t moved, no new pause has happened and you can skip the next step.Device B: discover
playback timestamp moved: GET /sync/playback returns all paused playbacks for this user (or narrow with /sync/playback/episodes / /sync/playback/movies if your UI only renders one kind). Save the new timestamp.Anime episode numbering
Anime and Western TV catalogs disagree about what an “episode” is. Simkl supports both numbering schemes and maps between them automatically — pick whichever IDs your source naturally exposes and scrobble with that. Simkl resolves to the same canonical record either way.Two numbering models
| Catalog | What’s an item? | How episodes are numbered |
|---|---|---|
| Anime-native — Simkl, AniDB, MAL, AniList, Kitsu, AniSearch, Anime-Planet, LiveChart, ANN | Each anime “cour” (season/arc) is its own title with its own ID. Attack on Titan, Attack on Titan S2, Attack on Titan S3 are three separate records. | Episodes restart at 1 within each title — no season field. |
| Western TV catalogs — TVDB, TMDB, IMDB | One franchise = one show with multiple seasons. Attack on Titan is one show, S1 / S2 / S3 are seasons of it. | Episodes numbered as season + number (e.g. S2 E4). |
Scrobble with whichever IDs you have
Anime-native (anidb / mal / anilist): pass the anime-cour ID and a plain episode number — noseason needed.
episode.ids. Prefer season + number whenever you can though: episode IDs can be re-issued when the source catalog merges duplicates or re-numbers a season, and a stale ID returns 404, while season + number is stable forever. See Standard media objects → Episode IDs.
How Simkl maps between them
Internally Simkl stores its own episode ID per anime title, mapped to the matching TVDBseason + number (and AniDB episode ID where available). When you scrobble using Western catalog season numbers (TVDB, TMDB, IMDB), Simkl walks that mapping to find the right per-anime episode; when you scrobble using any anime-native ID (anidb, mal, anilist, kitsu, anisearch, animeplanet, livechart) with a plain episode number, it goes straight to the canonical record.
Every scrobble response echoes both representations so your client can update either UI:
season/number— AniDB-canonical (per anime-cour numbering)tvdb_season/tvdb_number— the TVDB-style equivalent
| Field | Value |
|---|---|
season / number | 1 / 4 (per the Attack on Titan S2 anime title) |
tvdb_season / tvdb_number | 2 / 4 (per the Attack on Titan TVDB show) |
| canonical Simkl record | the same row either way |
Gotchas and FAQ
Should I poll progress every N seconds?
Should I poll progress every N seconds?
User seeks within the same item
User seeks within the same item
User scrubs back and forth across the 80% mark
User scrubs back and forth across the 80% mark
/scrobble/stop, not on every progress report. A user can scrub past 80% mid-playback as many times as they want without triggering the watched mark. The server just notes “watched” at the moment of stop if progress >= 80. So:- User pauses at 90% → saved at 90%, not marked watched
- User stops at 90% → action:
scrobble, marked watched - User stops at 75%, then resumes and stops at 95% → action:
scrobble, marked watched on the second stop - User stops at 95%, then later rewinds to 30% and stops there → previous watched state stays; the new pause at 30% is just a saved position
Connection drops mid-loop
Connection drops mid-loop
404 id_err on /scrobble/start
404 id_err on /scrobble/start
/search/id with the same external IDs you already sent — Simkl already tried those and failed. Realistic fallbacks:- Filename-based match — if your player knows the file path, call
POST /search/file. Filename uses different signals (release-group tags, year-in-name, etc.) than external IDs and may resolve where IDs didn’t. - Title text search — if you have a title but no good IDs, call
GET /search/{type}and let the user pick from results. - Cache the negative — if nothing resolves, remember it so you don’t keep retrying the same un-matchable item every playback.
- Ask the user — surface a “couldn’t identify this title” UI so the user can correct it manually.
Item not in Simkl's catalog at all
Item not in Simkl's catalog at all
User pauses without a prior start
User pauses without a prior start
Multiple titles played in one session
Multiple titles played in one session
Long-form content (3+ hour movies)
Long-form content (3+ hour movies)
User manually marks watched while my scrobble loop is active
User manually marks watched while my scrobble loop is active
stop won’t double-count — Simkl returns 409 if the same session has already been completed within the last hour.Cross-device resume — does it just work?
Cross-device resume — does it just work?
GET /sync/playback to fetch all saved playbacks (or narrow with /sync/playback/episodes / /sync/playback/movies if you only render one kind); you call /scrobble/start with the saved progress to resume.What if the user deletes the title from their watchlist?
What if the user deletes the title from their watchlist?
Stremio / future addon SDKs
Stremio / future addon SDKs
/scrobble/checkin for a fire-and-forget watching-now status.checkin vs start: when do I really pick checkin?
checkin vs start: when do I really pick checkin?
checkin when (a) you can detect that playback began but (b) you cannot reliably catch the stop — e.g. embedded players, casting flows, browser extensions where the page may close without a clean event. Simkl extrapolates progress from start time + runtime and auto-marks the item watched at 100%. You can still call /scrobble/stop later to override the auto-completion if you do catch a stop event.Auto-completion timing: When the computed progress reaches 100%, the title is auto-marked watched shortly after — typically within ~2 minutes. The delay is normal and consistent across all checkin sessions.Anime episode numbering
Anime episode numbering
season + number. Simkl supports both — see the Anime episode numbering section above for examples and the internal mapping.Reference
Request bodies
progress is a float from 0 to 100 with up to 2 decimal places. Send 75, 75.0, 75.12, or 75.00 — responses normalize (e.g. 75 not 75.00).
- Movie
- TV show
- Anime
- Checkin
Action types in responses
action | Returned by | Meaning |
|---|---|---|
start | /scrobble/start | Beginning or resuming playback. |
checkin | /scrobble/checkin | Simkl will auto-scrobble at 100% from the item’s runtime. |
pause | /scrobble/pause, or /scrobble/stop with progress < 80 | Session saved as a paused playback. |
scrobble | /scrobble/stop with progress >= 80 | Item marked as watched. |
Session lifecycle details
Only one active session per item per user
Only one active session per item per user
/scrobble/start (or /scrobble/checkin) for a new item replaces any existing session for that item and clears previous pauses.Auto-expiry
Auto-expiry
- Start and checkin sessions expire after the item’s remaining runtime.
- Stop sessions expire 1 hour after the call (used for duplicate prevention).
- Pause sessions expire immediately but persist as saved playbacks.
Rate limiting (the 20-second lock)
Rate limiting (the 20-second lock)
429-style throttling.Duplicate prevention
Duplicate prevention
409. This prevents accidental double-scrobbles. The 409 body contains watched_at and expires_at so you know when the protection ends.Managing paused playbacks
The user can browse their saved playbacks at simkl.com/my/history/playback-progress-manager. Programmatically:Get playback sessions
GET /sync/playback — list saved playbacks for resume UIs (or narrow with /sync/playback/:type where :type is episodes or movies).Delete a playback
DELETE /sync/playback/:id — remove a saved playback.See also
Start
POST /scrobble/start — show “Watching now”; resume a paused session.Pause
POST /scrobble/pause — save progress so the user can resume.Stop
POST /scrobble/stop — end session; >= 80 marks watched.Checkin
POST /scrobble/checkin — auto-mark watched at 100% based on runtime.Sync guide
Standard media objects
ids structure for movies, shows, anime, and episodes.