No auth required. Trending data is public — send the standard required URL parameters (
client_id, app-name, app-version) and a User-Agent header, but no user Authorization token.Linking back (websites only)
If your client can render hyperlinks — websites, browser extensions, web apps — link the title to the matching Simkl Most Watched page. TV apps, consoles, CLIs, and other contexts that can’t open external URLs are exempt; the title alone is enough.Most Watched Movies
simkl.com/movies/best-movies/most-watched
Most Watched TV
simkl.com/tv/best-shows/most-watched
Most Watched Anime
simkl.com/anime/best-anime/most-watched
_100) or top 500 (_500) items. Titles with the most watchers are returned first.
At a glance
Sizes
Top 100 (
_100) and Top 500 (_500) per file.Last-Modified
Each file’s
Last-Modified response header tells you exactly when it was generated.Update frequency
| Data | Description | Update frequency |
|---|---|---|
| Today | Most watched titles over the last 24 hours | Every hour |
| Week | Most watched titles over the last 7 days | Once a day |
| Month | Most watched titles over the last 30 days | Once a day |
| DVD releases | Latest popular DVD releases (Movies only) | Once a day |
Regeneration is best-effort, not strictly precise. The cadences above are targets — the actual job can take a few extra minutes (and occasionally longer) to finish. If your scheduled refresh hits a
304 Not Modified right at the expected time, the new file just hasn’t been generated yet; back off and retry in a few minutes rather than waiting another full cycle. See SimklTrendingClient — drop-in SDK for the recommended pattern.All trending data files
Append?client_id=YOUR_CLIENT_ID&app-name=my-app-name&app-version=1.0 to every URL below — the SimklTrendingClient does this for you from CONFIG; if you’re rolling your own, see Without the SDK.
Combined
Movies + TV + anime in a single response. Top-level keys:movies, tv, anime. Best when you want all categories at once and don’t want three round-trips.
| Timeframe | Top 100 | Top 500 |
|---|---|---|
| Today | today_100.json | today_500.json |
| This week | week_100.json | week_500.json |
| This month | month_100.json | month_500.json |
By type — Movies, TV, Anime
One category per file. Top-level response is the array directly (no wrapper object). Best when you only render one category, or want to fetch them independently.| Category | Timeframe | Top 100 | Top 500 |
|---|---|---|---|
| Movies | Today | movies/today_100.json | movies/today_500.json |
| Movies | This week | movies/week_100.json | movies/week_500.json |
| Movies | This month | movies/month_100.json | movies/month_500.json |
| TV | Today | tv/today_100.json | tv/today_500.json |
| TV | This week | tv/week_100.json | tv/week_500.json |
| TV | This month | tv/month_100.json | tv/month_500.json |
| Anime | Today | anime/today_100.json | anime/today_500.json |
| Anime | This week | anime/week_100.json | anime/week_500.json |
| Anime | This month | anime/month_100.json | anime/month_500.json |
DVD releases (movies only)
Latest popular DVD releases — mirrors Simkl DVD Releases. No timeframe split; refreshes once a day.| Top 100 | Top 500 |
|---|---|
| dvd/releases_100.json | dvd/releases_500.json |
Copy-paste JSON
Drop this object into your app as a constant. ReplaceYOUR_CLIENT_ID with your registered app’s client_id and my-app-name / 1.0 with your app’s name and version — both the URL params (required_query) AND the matching User-Agent header (user_agent_header) are required on every request, free or paid plan.
Each entry has a title (attribution-compliant display label), a url (bare path — append ? + required_query when you fetch), and a refresh_seconds (how often the file is regenerated server-side — set your refresh timer to this value). The structure is category → timeframe → size so you can filter or iterate by any axis. DVD has no timeframe.
- Titles are pre-composed to satisfy the attribution requirement (every label includes both “Trending” / “Most Watched” and “Simkl”). Use them as-is or adapt the wording — just keep both halves.
catalogsis the default row list the SDK renders on a home screen — 44 rows out of the box, all titled and ready to display. Mix of: trending (movies/tv/anime today + DVD), sort variants (byRank,highestRated,mostAnticipated× 3 types), composed catalogs (hiddenGems× 3,criticallyAcclaimed× 2,marathonWorthy,bestBoxOffice), Best of (Netflix / HBO / Disney+ / Prime Video / Apple TV), Best of for movies (Action / Sci-Fi / Comedy / Horror / Thriller / Drama), TV (Drama / Comedy / Crime / Sci-Fi), anime (Shounen / Isekai / Romance), and time-based filters (Quick Watches, In Theaters Now, Just Released on DVD, Currently Airing TV, Anime Movies, Anime OVAs). Edit the JSON to remove rows you don’t want, change titles, or swap recipe opts. Per-app override still works viacfg.catalogsin the SDK config. Currently the recipes ship in the Node + Browser tabs below; Python / Swift / Kotlin / Dart will render the derived rows as raw items until those tabs gain recipe ports (or you supply atransformclosure).refresh_secondsis3600(1 hour) fortodayfiles and86400(1 day) for everything else, matching the Update frequency table. This is the nominal regeneration cadence, not a guarantee — server regeneration can take a few minutes longer. For bandwidth-optimal refresh, pair this with the response’sLast-Modifiedheader and sendIf-Modified-Sinceon subsequent requests — the server returns304 Not Modifiedwhen nothing has changed. If a refresh scheduled forrefresh_secondsreturns 304, back off and retry rather than assuming the cache is final (see SimklTrendingClient — drop-in SDK).
simkl-trending-urls.json in your project — the SDK class loads it back into a SIMKL_TRENDING_URLS symbol. Each language tab’s top comment shows the runtime-appropriate one-liner.
SimklTrendingClient — drop-in SDK
Two-step setup before pasting the SDK class below. First, save the Copy-paste JSON block above as
simkl-trending-urls.json in your project (or as an asset your runtime can load). Second, load it back into a SIMKL_TRENDING_URLS symbol — each tab’s top comment shows the runtime-appropriate one-liner (import / require / fetch for JS, open() for Python, Bundle.main for Swift, context.assets for Android, rootBundle for Flutter). The constructor throws if neither this symbol nor an explicit urls argument is in scope.CONFIG block at the top of each tab — everything else is plumbing you should not need to edit. What’s built in:
- Auto-picks the Combined endpoint when you ask for 2+ of movies/tv/anime — one HTTP call per timeframe instead of two or three.
- Persistent cache keyed on the URL — relaunch can paint the UI instantly from the last persisted snapshot before any network call fires. JS auto-detects the host (browser →
localStorage[storageKey], Node → file atpersistPath); other languages use platform-native paths. Cache entries older thanpersistMaxAgeSecondsare dropped on restore.client.clearCache()wipes both in-memory and persistent state (and notifies subscribers so the UI repaints empty). For server-side runtimes with many processes, swap the_persist/_restorePersisted/clearCachetrio for a Redis / KV adapter. - Conditional revalidation with
If-Modified-Sinceso refreshes hit the CDN with a cheap 304 when nothing has changed. - Regen-lag backoff — when the server hasn’t regenerated by the time
refresh_secondselapses, the client backs off via a[5min, 15min, 30min]ladder instead of hammering on the nominal cadence. - Jitter — ±5% random jitter on every refresh + ±10% on retry delays, so all clients don’t hit the CDN at exactly
:00and don’t retry-storm in lockstep. - Retry with exponential backoff + per-request timeout +
Retry-Afterhonored for network blips and429s. - Permanent-error classification —
404/400/401/403are not retried (they’d waste the backoff schedule on a request that will never succeed); the entry’s loop halts and you see the error in the log. - Process-wide
minIntervalMsfloor — N entries refreshing at the same tick are serialized through a 250 ms (configurable) gate so you never burst N parallel requests at the CDN. - Concurrent fetch dedup — two callers asking for the same URL fold into one in-flight request.
- Cancellation —
client.stop()aborts every in-flight request and clears every timer. - Subscribe / get — your UI subscribes to a type and receives the current cached items immediately, plus a callback on every successful refresh.
get(type)is synchronous — paginate the returned array locally for UI pages instead of refetching. client.tick()for background schedulers — call from aBGTaskScheduler/WorkManager/workmanager/ cron periodic task to perform a single revalidate-and-persist pass.- Namespace-safe — every public symbol is prefixed
Simkl…(SimklTrendingClient,SimklTrendingConfig,SimklTrendingError,SIMKL_TRENDING_URLS, etc.) so dropping the class into an existing app doesn’t collide with your ownConfig/Client/Entrytypes.
Try it live
A self-contained vanilla-JS demo runs in CodePen below — same SDK, all 55 default catalog rows, two live fetches againstdata.simkl.in: trending/today_500.json (combined movies/TV/anime in one round-trip) and dvd/releases_500.json. Click Result to see the rendered home screen; JS / HTML / CSS tabs show the wiring.
Open & fork in CodePen
Edit the catalog config, try other recipes, swap the test
client_id for your own.Node 22+, Deno, Bun, React Native, Cloudflare Workers, and other fetch-based JS runtimes — the JavaScript tab above (the SDK file) is universal: save it verbatim as Bundle
simkl-trending-client.js, then wire it up from your app entry point in ~6 lines. The Browser tab right above it is the HTML+DOM wrapper you’d skip in a headless runtime; here’s the non-browser equivalent:app.mjs (or index.js App.tsx)
simkl-trending-urls.json with your app (or fetch + cache it once at startup) so the catalog is in scope when SimklTrendingClient runs. For Cloudflare Workers / Lambda / GitHub Actions etc., call await client.tick() from your scheduler instead of client.start() — see the “Background scheduling” comment at the bottom of the JavaScript tab.What you still need to do per-platform. The
SimklTrendingClient samples above wire up everything that’s host-agnostic — auto-pick combined, persistent cache, conditional revalidation, regen-lag backoff, jitter, retry with exponential backoff + timeout, in-flight dedup, cancellation, and subscribe/get. Two things are left to you because they depend on your platform:- Background scheduling that survives app lifecycle. In-process timers stop firing when an iOS app is backgrounded or after Android process death. Hook your platform’s periodic task system to
client.tick()—BGTaskScheduler(iOS),WorkManager.PeriodicWorkRequest(Android), theworkmanagerpackage (Flutter), ornode-cron/ Cloudflare Cron / AWS EventBridge for server-side runtimes. Each tab’s trailing comment shows the wireup. - Attribution back-link. Websites must link the rendered section title to the matching
simkl.com/<type>/best-*/most-watched/page — see Attribution required and Linking back.
Codable, Kotlin @Serializable, or Dart json_serializable.Catalogs — the rows on your home screen
Think of a TV-style discovery UI: rows of poster carousels stacked vertically, each row with a title at the top-left. The SDK exposes that asclient.catalogs() → [{id, title, items}, ...] and client.subscribeCatalogs(cb) — available in all 5 languages above. Each row has a stable id, an attribution-compliant title, and an items array. You iterate and render.
Three places rows can come from, in priority order:
cfg.catalogsin your SDK config — explicit, wins if set. Use this to mix trending + derived rows with customtransformclosures (see below).catalogs[]in the Copy-paste JSON above — the default list of rows. Already populated with one row per type (movies_today,tv_today,anime_today,dvd). Add, remove, or reorder entries in the JSON to change defaults across your whole app — no code change.- Auto-built fallback — if neither of the above is set, the SDK builds one row per
supportedType×timeframes[0]from your CONFIG.
new SimklTrendingClient() immediately gives you 4 ready-to-render rows — no extra config needed. Override only when you want custom rows.
Adding derived rows in code — when you want filter/sort logic that’s not in the JSON (e.g. “Hidden Gems”), pass cfg.catalogs with a transform function on each entry:
client.catalogs() returns the resolved list on demand. Same {id, title, items} shape in every language.
Available recipe transforms (drop into transform: to derive a row). Each has tunable opts as the 2nd arg — hiddenGems(items, { minRating, minVotes, minRank, source }), etc. Dial them to your taste without forking.
| Group | Recipe names |
|---|---|
| Sorts | mostWatchlisted, highestRated, stickiest, newest, bestBoxOffice |
| Filters | byGenre, byGenreAll, byCountry, byNetwork, byStatus, byAnimeType, withTrailer, quickWatches, marathonWorthy, miniSeries, justReleased, inTheatersNow, recentlyOnDvd, ongoing, recentlyEnded, justPremiered, byYear, byDecade, currentYear, lastYear |
| Composed | hiddenGems, criticallyAcclaimed, crossPlatformConsensus, massiveAnticipation, bestOfNetwork, bestOfGenre, bestOfDecade |
| Groupings | groupByGenre, groupByCountry, groupByNetwork, groupByDecade (these return an object of buckets — convert to rows with Object.entries(R.groupByGenre(items)).map(([genre, items]) => ({ id: 'genre_' + genre, title: 'Trending ' + genre + ' on Simkl', items }))) |
Adding / reordering rows costs nothing. Catalog rows are resolved at read time off the cached items. Changing
cfg.catalogs and recomputing produces a new row list in microseconds. The SDK refreshes the underlying cache on its own cadence (~1h for today, 1d for week/month), independent of how many rows you render.hiddenGems 67 movies / 156 TV / 204 anime · criticallyAcclaimed (default 8.5+ rating, 1k+ votes) 15 movies · marathonWorthy 75 TV · crossPlatformConsensus 66 movies · bestBoxOffice 449 movies parseable · quickWatches 38 movies under 90 min · byNetwork(tv, 'Netflix') 64 · byCountry(anime, 'jp') 361. Defaults are intentionally conservative — relax them if you want fewer empty rows.
Catalog ideas — recipe logic at a glance
TheSimklTrendingRecipes namespace is shipped Node-only. If you’re porting to another language (the SDK class scaffolding above already covers Python / Swift / Kotlin / Dart), here’s the logic for each recipe — drop it into your transform: / client.get(type) pipeline and you have the row.
| Catalog row | Logic |
|---|---|
| Most Watchlisted | sort plan_to_watch desc — items that appear on the most user watchlists. The name “Most Anticipated” misled here: this fires for already-released titles too (huge backlog appeal — Game of Thrones, Avatar, One Piece all rank high). Use this for the universal “what’s on people’s minds” row. |
| Highest Rated (Simkl) | sort ratings.simkl.rating desc, gate on ratings.simkl.votes >= 500 to drop noise |
| Highest Rated (IMDb / MAL) | same, on ratings.imdb.rating (movies/TV) or ratings.mal.rating (anime) |
| Stickiest | sort parseFloat(drop_rate) asc — viewers who started, finished, and didn’t bail |
| Newest releases | sort by parsed release_date desc — release_date is MM/DD/YYYY (e.g. 03/26/2026), which new Date(...) / Python datetime.strptime("%m/%d/%Y") / Swift DateFormatter("MM/dd/yyyy") all accept |
| Box-office hits (movies) | extract $NNNM from metadata, sort desc |
| Quick watches (movies) | parse runtime to minutes, filter ≤ 90, sort by rating |
| Marathon-worthy (TV/anime) | filter status === 'ended' AND 20 ≤ total_episodes ≤ 100, sort by rating |
| Mini-series (TV/anime) | filter total_episodes ≤ 6 |
| By genre / country / network / status / anime type | items.filter(...) on the matching field |
| In theaters now (movies) | filter theater date within last 30 days |
| Just released on DVD (movies) | filter dvd_date within last 14-30 days |
| Just premiered (TV/anime/movies) | filter status === 'premiere' — flags items whose first episode JUST aired (typically within ~30 days). Not an “upcoming premieres” filter; the data has no field for that. |
| By year (e.g. Movies of 2026) | parse the 4-digit year out of release_date (any format), filter equality |
| By decade (e.g. Best of the 90s) | parse year, filter decade ≤ y < decade + 10 |
| Current year / last year | same as By year, comparing against new Date().getFullYear() (or your language’s equivalent) — re-evaluated per call so the row stays current without re-shipping the JSON |
| Best of decade | combine By decade with bestOfNetwork’s shape — rating-sort + votes >= 100 floor inside the decade window |
| Has trailer | items.filter(i => i.trailer) — for trailer-first carousel UIs |
| Hidden Gems | filter rank > 2000 AND rating >= 7.5 AND votes >= 500, then sort by rating desc — well-loved titles OUTSIDE Simkl’s all-time top 2000 by popularity. Don’t filter by watched on trending feeds: that field is last-24h viewers, not lifetime, so a 150-cap surfaces mainstream titles like The Office or Interstellar. Rank is the right popularity gate; mainstream lives in rank 50-500, gems live in 2000+. Tune minRank up (3000, 5000) for stricter obscurity. |
| Critically acclaimed, widely seen | rating >= 8.5 AND votes >= 1000 — high bar on both axes |
| Massive anticipation | plan_to_watch / (watched + 1) >= 300 — pre-release / newly-announced (median ratio is ~200; 300 is upper quartile) |
| Cross-platform consensus | Simkl rating ≥ 8 AND IMDb (or MAL) rating ≥ 8 — both crowds agree |
| Best of | filter network match + status === 'ended', sort by rating |
| Grouped by genre / country / network / decade | bucket items into { "Action": [...], "Drama": [...] } for N rows from one file |
What’s in each item
Per-title arrays (movies, tv, anime) all share the same base shape, with a few type-specific fields.
Sample item
Field reference
Display title.
Path on simkl.com (e.g.
/movie/2533163/the-drama). Prepend https://simkl.com to deep-link.Image path fragment. Combine with the prefixes in Image conventions — for example
https://wsrv.nl/?url=https://simkl.in/posters/{poster}_m.webp&q=90. When null, fall back to https://simkl.in/poster_no_pic.png (see fallbacks).Image path fragment for fanart. Same prefixing rules as
poster. When null, hide the fanart element — there’s no dedicated fanart placeholder.External and Simkl IDs. Always carries
simkl_id + slug. tmdb is near-universal; imdb appears on TV / movies, mal / anidb / anilist / kitsu on anime. Additional third-party slug variants (letterslug, traktmslug, tvdbslug, trakttvslug, mdlslug, …) may appear on items with those platform links — the response is permissive.Simkl service rank for this media type.
Number of users who watched this title in the timeframe.
Number of users with this title on their Plan-to-Watch watchlist.
Percentage of users who started and dropped, formatted as a string (e.g.
"1.6%").Aggregate ratings — Simkl and any external sources available for this title (IMDB for movies/TV, MAL for anime, etc.). Each entry is
{ rating: number, votes: number }.Original release date in
MM/DD/YYYY format.ISO-style country code (e.g.
us, jp).Human-readable duration (e.g.
1h 45m, 25m).One of
ended (production wrapped), ongoing (TV/anime still airing new episodes), or premiere (very-recently-released, still in the rollout window). The wider Simkl catalog also uses tba for not-yet-dated titles, but those don’t surface in trending.Array of genre tags.
YouTube video ID.
Synopsis.
Pre-formatted human-readable summary line (release year, budget, box office, network, etc.).
Movies only. DVD release date (
MM/DD/YYYY).Movies only. Theatrical release date (
MM/DD/YYYY).Anime only. One of
tv, movie, special, ova, ona, music video.TV / anime. Episode count.
TV / anime. Broadcasting network.
Without the SDK — raw URL and curl
TheSimklTrendingClient above is the recommended way to consume trending data — it handles caching, conditional revalidation, retry, dedup, persistence, and the auto-pick combined endpoint logic. The patterns below are the bare-bones alternative for ad-hoc curl probes, server-side cron jobs that handle their own caching, or runtimes where you don’t want to drop a class file.
URL pattern and quick curl / JS / Python / Go examples
URL pattern and quick curl / JS / Python / Go examples