New featureEpisode runtimesEvery episode now has a
runtime in whole minutes, or null when none is on file:GET /tv/episodes/{id}andGET /anime/episodes/{id}— on every episode.GET /sync/all-items— addepisode_runtime=yes(withextended=full) to get it on each episode row.- Calendar v2 files — on every
calendar[].episode. This is an integer, unlike the show-levelmetadata.runtimedisplay string ("22m").
GET /movies/{id}, /tv/{id} and /anime/{id} now return similar: titles in the same genres, as shown at the bottom of a title’s page on simkl.com. It is omitted when there are none. Items in similar, users_recommendations and relations also carry fanart now.Custom Lists: collections and rolesGET /lists/{id}returnscollection— the sibling lists this one belongs with, such as a franchise split into Movies and TV Shows, ornull. See Lists that belong together, and Custom List Collections for how users create one.GET /lists/user/{userId}gives every list arole—owner,collaboratororfollower— so followed and shared lists can be told apart.- Auto lists set to random order are not available through the API and return
400; see the FAQ.
metadata now include watching and completed — all-time counts of users with the title in each list. On trending files, watched still counts only the file’s timeframe; use watching + completed for a title’s overall audience.Major launchNew featureCustom lists are readable from the APITwo endpoints are live in beta: Gate on
GET /lists/user/{userId} for a user’s lists, and GET /lists/{id} for a list’s contents. Creating, editing and deleting lists, and adding or removing items, remain web-only for now.Requires AUTH V2 and a Simkl PRO or VIP access token. This is the first API that V1 client_ids cannot reach, and new APIs from here on will be built the same way — see Migrating from V1 to V2. There is no anonymous access either; an anonymous caller counts as free.Don’t A free or anonymous caller gets HTTP 200, not an error. The body carries {"error": "premium_only", ...} with no items array and no pagination. A client that branches only on the status code will treat it as a successful list and render the placeholder card as real content.Check for an error key on a 200 before reading items. The item object it carries is renderable — title, description, poster — so you can show it in place of the list rather than leaving the user with a blank screen.There is a polling gate — use it. (Added 2026-09-21.) GET /sync/activities carries a custom_lists block, so you never have to poll the list endpoints on a timer to find out whether anything changed:all, then branch on the four buckets to refresh only the kind that moved — a user favouriting one show moves favorites alone, so it costs one list refresh rather than a full re-read. Three things to know:- It is nested one level deeper than the other blocks:
custom_lists.lists.all, notcustom_lists.all. - The bucket is
favorites(plural) while a list’s owntypefield isfavorite(singular). They do not match. autodoes not move when an auto list’s contents are rebuilt — only when the user edits its settings. Auto lists regenerate about once a day, so refresh those on your own schedule rather than waiting for a signal that will not come.
New featureMigration required
AUTH V2 is here, and every app needs a new client_id
Your V1client_id cannot be upgraded. There is no conversion and no toggle — V2 is a separate registration with a new client_id, and your existing V1 credentials are rejected by every /oauth2/* endpoint with 401 invalid_client.What you get
AUTH V2 is a standards-compliant OAuth 2.0 implementation. Stock OAuth libraries work against it without Simkl-specific handling, and it publishes RFC 8414 discovery metadata most of them can configure themselves from.Two flows replace the three in V1:OAuth flow
Anything that can open a browser — web, mobile, SPA, desktop, extensions. PKCE is mandatory for every client type,
S256 only.Device / PIN flow
TVs, consoles, watches and CLIs, with an 8-character code (RFC 8628). Replaces the V1 PIN flow.
Request quotas are now counted per user
Each user brings their own daily allowance, set by their own Simkl plan:There is no app-wide quota to manage or exhaust. A V1 app has one allowance covering everything it does however many users it has, so a single heavy user can spend the budget the rest of them needed. On V2 your capacity grows with your user base instead of being divided between it, and one user can no longer spend another user’s allowance. See Rate limits.
Decide what happens before sign-in
The work, in order
For most apps the work is confined to your auth code — you are not rewriting your sync logic.Register a new V2 app
Register in developer settings and choose a client type, which is fixed at registration:
- Mobile, desktop & browser apps
- TV, devices & command line
- Server apps & services
Move your auth code across
Swap
/oauth/* for /oauth2/*, add PKCE, request the scope you need, and add refresh handling. Migrating from V1 to V2 has the full sequence.Ship the new client_id early
Your V1 app keeps working the whole time, so you can run both side by side while users move across.If your app is distributed rather than server-side, get the new
client_id into the field early — a build that only learns it in a later release cannot migrate anyone still running an older one, and that is the thing most likely to decide whether you finish on time.Token and permission changes
See Tokens and refresh for the refresh grant and revocation.
Three behaviours that each cost an afternoon
-
Omitting
scopegrants read-only, and so does misspelling it. An unrecognised scope string is silently treated as read-only rather than rejected, so the failure surfaces much later as a403on your first write. Read thescopein the token response rather than assuming you got what you asked for. -
A failed token exchange consumes the authorization code. Once the request passes client authentication, the code is burned before the grant is validated, so you cannot correct the request and retry the same code. The one exception is
401 invalid_client— client authentication runs first, so a wrong secret leaves the code alive and you can retry it inside the 10-minute window. - Refreshing invalidates the previous access token immediately. Two processes that independently refresh the same grant will keep cutting each other off. Either nominate one refresh owner that writes the new token to shared storage, or give each instance its own authorization.
New featureRewatches now work while scrobbling
?allow_rewatch=yes used to be limited to POST /sync/history and GET /sync/all-items. Players can now record rewatches directly from the scrobble loop, on POST /scrobble/stop and POST /scrobble/checkin. Simkl PRO / VIP only, and the flag is a query parameter, not a body field.Without it, scrobbling something the user already finished records nothing. The server sees a duplicate watch and drops it, so their rewatch count never moves.- On
stopthe rewatch is immediate, but only whenprogressis 80 or higher, the same threshold that marks the item watched in the first place. A stop below 80 resolves toaction: "pause", marks nothing, and returns no rewatch fields at all, flag or no flag. The response gainsrewatch_idandrewatch_status. Branch on the status, never on the id: a session that is already running reports its id even when it rejected your watch.active/completed/closedmean it saved.first_watch,too_soon,not_eligible,pro_requiredandnullexplain why it didn’t. Note this is a superset of the three-valuerewatch_statuson/sync/all-items, so widen your enum if you share one. - On
checkinit is deferred. A check-in marks nothing watched at request time, so Simkl stores the opt-in and applies it when the check-in matures at the end of the item’s runtime. The response carries no rewatch fields, so read the session back fromGET /sync/all-items?allow_rewatch=yesif you need to confirm it. The PRO / VIP check runs at check-in time, and the choice is frozen once the check-in is recorded.
/scrobble/start. That endpoint auto-finishes the user’s previous unfinished playback before opening a new one, and it honours the flag while doing it. It can log a rewatch of a different title, with nothing in the response to tell you. Gate the flag behind explicit user intent, and ship a Track rewatches setting that defaults to off.Also changed: GET /sync/playback now returns in-progress re-viewings. A paused playback on a title the user already finished is no longer hidden, as long as the pause happened after they finished it. Continue Watching rows can therefore contain completed titles. That is intended, and hide_watched=true (still the default) deliberately leaves them in. It only removes playbacks that were overtaken: paused first, then marked watched elsewhere. If your app worked around the old behaviour, drop that workaround.Full walkthrough, including the four gates to check before enabling and the 2-day gap between watches, in the Rewatches guide, Scrobble section and the Scrobble guide.BreakingCalendar data files v2 — old files stop updating 1 February 2027A new version of the calendar data files is live under a Deadline: the old files (same paths without
/v2/ path segment, for all three catalogs and for both the rolling and monthly files:/v2/) keep regenerating until 1 February 2027. After that date they stop updating permanently and will serve increasingly stale data. Update your code before then.What’s different in v2:- Split payload. Each file is now one object with two keys instead of a flat array —
calendar(one entry per airing or release, chronological) andmetadata(one record per distinct title, keyed by Simkl ID). A show airing 12 episodes this month appears 12 times incalendarbut only once inmetadata, so titles, posters, IDs, ratings, and genres are stored once rather than repeated on every airing. Join the two onsimkl_id. - All timestamps are UTC. Every
dateis now ISO 8601 with a trailingZ. The old files carried per-catalog offsets (-05:00for US TV,+09:00for anime), which forced clients to normalize before sorting or comparing. - Finale markers. A new
finale_typefield flags mid-season (1), season (2), and series (3) finales so you can badge them,nullotherwise. - Much richer metadata. Each title now carries alternate titles, fanart, popularity rank, drop rate, watched and plan-to-watch counts, ratings from Simkl plus IMDb or MAL, country, original language, runtime, status, network, genres, and trailer — plus
title_romajiandanime_typeon anime, anddvd_dateon movies.
DocsTrending SDK with live previewA new Trending SDK page shows how to combine filters and sorts on top of our cached top-500 trending feeds. Drop-in JS file (also ported to Python, Swift, Kotlin, and Dart) plus an embedded CodePen you can fork.
- Two CDN fetches → full home screen — combined movies + TV + anime in one round-trip, plus DVD releases.
- 55 default catalog rows ready to render with attribution-correct titles — Trending Today, Most Watchlisted, Top Rated, Hidden Gems, Just Premiered, Movies This Year, Best of Netflix / HBO / Disney+ / Prime / Apple TV, Best Action / Drama / Sci-Fi movies, Best of the 2020s / 2010s / 2000s / 90s, Marathon-Worthy TV, Quick Watches, Anime Movies and OVAs, and more.
- Tunable recipes (
hiddenGems,bestOfNetwork,bestOfGenre,bestOfDecade,currentYear,justReleased,marathonWorthy, …) — override knobs per row viarecipe_optsor supply a customtransformclosure. - Persistent cache (localStorage in browsers, file in Node), conditional 304 revalidation, retry with backoff, request dedup, reactive subscribe / get API. No build step, no framework.
- Works as-is in browsers, Node 22+, Deno, Bun, React Native, and Cloudflare Workers.
Major launchRewatches via
?allow_rewatch=yesPOST /sync/history and GET /sync/all-items now track rewatches as separate sessions when the caller opts in with ?allow_rewatch=yes. Simkl PRO / VIP only.- Write: post episodes (or whole items) the user is rewatching. The server auto-detects rewatch intent, or you can force it with
is_rewatch: true. Each session has its ownrewatch_id,rewatch_status(active/completed/closed), and per-episode timestamps. - Read: any item with saved sessions appears multiple times in the response — its normal entry plus one rewatch row per session, each carrying
is_rewatch: trueand the session fields above.
Major launchAPI Analytics: live per-app request logEvery registered Simkl app now has a built-in Analytics view at /debug/api-analytics showing exactly what your code sent to
api.simkl.com. Up to 24 hours of individual requests, with edge and origin status codes, latency, burst detection, and CSV export.What you can see:- Per-request rows with method, path, query string, edge HTTP status, origin HTTP status, origin latency, cache status, country, and User-Agent. Each row has a Link column that re-fires the same
GETin a new tab so you can read the live response body. - Burst detector that flags 3+ requests from the same IP in one second with a red
BURSTbadge. Helps you catch retry loops and runaway timers before the auto-blocker fires. - Filters with
equals,contains,starts with,ends with, andinoperators over every column. Common recipe:Origin contains 4to surface 4xx failures. - CSV export of the current filtered view for sharing with support or attaching to bug reports.
- One-click mask for sensitive params: hit a button to redact
code,access_token,refresh_token,pin, and similar values as***before screenshotting or sharing the view.
- Open simkl.com/settings/developer/ while logged in.
- Click your app’s row.
- Click Debug (top-right of the app card). The analytics view opens in a new tab.
date_from, silent 4xx errors your HTTP client never surfaces, and stale edge-cache hits.Full reference at API Analytics.DocsDocs revampApiary is retired. Oracle is shutting it down in October, and Apiary has been going down for hours at a stretch lately on top of that, so we used the forcing function to redo everything from scratch. The docs now live at api.simkl.org. Most of the content is new: what was supposed to be a content migration turned into a top-to-bottom audit when we found significant drift between the old docs and the actual API.What’s new:
- Detailed workflow guides: Sync, Scrobble, Search, Anime, Deep linking, Mark as watched, and the new Rewatches guide.
- All three auth flows properly walked through at Authentication: OAuth 2.0 for server-side web apps, Public PKCE for mobile, SPAs, browser extensions and desktop binaries, and PIN flow for TVs, consoles, CLIs, and media-server plugins.
- Conventions pages for the cross-cutting topics Apiary never covered: Headers, Pagination, Dates, Watchlist statuses, Null values, Images, Standard media objects, Extended info, and CORS.
- Flat Errors and status codes page grouped by HTTP status with cause / fix / example for each.
- Interactive try-it-now playground on all 48 endpoints across 14 categories. Drop in your token, hit Send, get a real response back.
- Copy-paste examples in 16 languages: curl, Python, JS/TS, Kotlin, Swift, Java, Go, C#, Ruby, PowerShell, C/C++, and more.
- LLM-friendly: every page can be copied as raw markdown or opened straight in Claude / ChatGPT / Cursor / VSCode. Full-site
llms.txtandllms-full.txtare published. - Full OpenAPI 3.1 spec: every endpoint and every response shape verified against the live API. Point your SDK generator at it.
- One-page endpoint index with all 48 endpoints across 14 categories at a glance.
- Default three-column layout site-wide (left sidebar, content, right-rail TOC) so every page has navigable section anchors.
BreakingSee the updated Quickstart and Headers and conventions.
app-name and app-version are now requiredEvery API request must now include app-name and app-version URL parameters alongside client_id, plus a descriptive User-Agent header. This lets us help debug issues faster and prevents accidental blocks.DocsRenamed in the docs: “List” → “Watchlist”Simkl’s website introduced Custom Lists — user-created collections of titles. To avoid confusion with the existing 5-status bucket system (Watching, Plan to Watch, Hold, Dropped, Completed), the API docs now refer to that bucket system as the Watchlist instead of “List”.Nothing changed at the API level. The endpoint URL
/sync/add-to-list is unchanged, request/response shapes are unchanged. Only the docs use the term “Watchlist” now — e.g., the endpoint is referred to as “Add to Watchlist” in the sidebar and prose.A dedicated Custom Lists API may ship in a future release.New featureNew: Pre-built trending JSON filesWe’ve published trending data as static JSON on
data.simkl.in — no API key required (User-Agent and attribution still required). Useful for “What’s hot right now” surfaces without paying any per-user request budget.- Top 100 and Top 500 lists
- Today, This Week, and This Month timeframes
- Combined, Movies, TV Shows, and Anime categories
- Latest popular DVD releases list
- Full title info with multiple IDs
New featureManual “fire-and-forget” trackingNew endpoint:
POST /scrobble/checkin. Send one request when the user starts watching; Simkl handles the “watching now” bar, runtime tracking, and auto-completion when runtime elapses. One call, then nothing else — useful when you don’t have real player events to drive /start / /pause / /stop.Use cases: cinema apps, live-TV trackers, social “watching now” status updates. See Scrobble guide.
ImprovementPIN poll response — two distinct shapesThe
/oauth/pin/{USER_CODE} polling response now returns one of two explicit JSON shapes (instead of a single shape with mixed semantics): { "result": "KO", "message": "Authorization pending" } while the user hasn’t entered the code yet, and { "result": "OK", "access_token": "..." } once they approve. Simpler client-side state machine — branch on result.See PIN authentication for the response schemas.Major launchMajor: real-time scrobble + cross-device playbackTwo big additions for media-player integrations:
- Scrobble API —
POST /scrobble/start,/pause, and/stopfor real-time playback tracking with progress percentages. See the Scrobble guide. - Playback API —
GET /sync/playback/{type}andDELETE /sync/playback/{id}to read and clean up paused sessions across devices. GET /sync/activitiesnow includes aplaybacktimestamp per type, so you know when to refetch playback state.- New web UI: Playback progress manager — users can now manage their saved sessions in-product.
New featureMonthly archive endpointsThe Calendar JSON now includes per-month archives for the last 12 months at the URL pattern
https://data.simkl.in/calendar/{YEAR}/{MONTH}/{tv|anime|movie_release}.json. The current month regenerates every 6 hours; previous months once a day.DocsRecommended image hosting: wsrv.nlWe now recommend serving Simkl image paths through wsrv.nl, a free image proxy that caches, resizes, and converts on the fly. This minimizes load on Simkl’s image servers and gives you free transformations like WebP conversion.See Images for the URL pattern and size codes. Examples append
&q=90 for origin-equivalent quality (wsrv’s default &q=80 reduces filesize and quality).Improvement
runtime (minutes) on shows and movies/sync/all-items responses now include a runtime integer field on show and movie items — minutes per episode for shows, total runtime for movies. Useful for “time to finish”, watch-time stats, and pacing surfaces.ImprovementUseful when you’re sending titles by TMDB ID and need to know how Simkl classified the item — e.g., a TMDB movie that Simkl resolved as an anime movie. Also handy for clients that store
simkl_type and anime_type in /sync/history responseThe per-item response object inside added.statuses now includes simkl_type (movie / tv / anime) and anime_type (tv / special / ova / movie / music video / ona).simkl_type locally so they can later reconcile deletions across types.ImprovementComplete a show in one callTwo additions to See
POST /sync/history:status: "completed"— pass on a show item to set the watchlist status directly. If the show is still airing, Simkl keeps it in Watching instead and reflects the resolved status in the response.use_tvdb_anime_seasons: true— pair withstatus: "completed"on anime to auto-mark every season watched, using TVDB/TMDB seasonal numbering. Lets clients that index against TVDB skip the AniDB season-mapping step.
statuses array showing the resolved per-item status:POST /sync/history.