Skip to main content
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.
Which IDs can I send/expect? All accepted input identifiers and the keys you’ll see echoed back in responses are listed at Standard media objects → Supported ID keys. Send every ID you have on writes — Simkl picks the first that resolves and ignores the rest. Reminder: slug is response-only (never send it on a request).
Attribution required. When you display trending data in your app or website, the section title must include Simkl alongside Trending (or an equivalent like Most Watched / Popular). Any sensible combination works — feel free to invent your own wording, as long as both ideas appear together. A few examples to get you started:For commercial use without attribution, contact us — we’re happy to discuss licensing.

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
Simkl provides pre-built JSON files with trending data ranked by the number of watchers. These are the same rankings displayed on Simkl’s Most Watched pages for Movies, TV Shows, and Anime. Each file is available in two sizes: top 100 (_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

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.
The file URLs ignore all query strings. Don’t add ?random=... or ?nocache=... — the CDN treats every variant as the same resource. Simkl regenerates the files on the schedule above and automatically clears them from the Cloudflare cache, so you’ll always get the latest version on your next request — client-side cache-busting won’t deliver newer data.
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.

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.

DVD releases (movies only)

Latest popular DVD releases — mirrors Simkl DVD Releases. No timeframe split; refreshes once a day.

Copy-paste JSON

Drop this object into your app as a constant. Replace YOUR_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.
  • catalogs is 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 via cfg.catalogs in 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 a transform closure).
  • refresh_seconds is 3600 (1 hour) for today files and 86400 (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’s Last-Modified header and send If-Modified-Since on subsequent requests — the server returns 304 Not Modified when nothing has changed. If a refresh scheduled for refresh_seconds returns 304, back off and retry rather than assuming the cache is final (see SimklTrendingClient — drop-in SDK).
Using the SDK below? Save this whole object as 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.
A single class that wires up the entire data flow. Tune the 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 at persistPath); other languages use platform-native paths. Cache entries older than persistMaxAgeSeconds are 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 / clearCache trio for a Redis / KV adapter.
  • Conditional revalidation with If-Modified-Since so 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_seconds elapses, 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 :00 and don’t retry-storm in lockstep.
  • Retry with exponential backoff + per-request timeout + Retry-After honored for network blips and 429s.
  • Permanent-error classification404 / 400 / 401 / 403 are 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 minIntervalMs floor — 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.
  • Cancellationclient.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 a BGTaskScheduler / 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 own Config / Client / Entry types.
The only thing left to wire up per-platform is background scheduling (the OS-level periodic task that keeps the cache warm while your app is suspended) — see the comment at the bottom of each tab.

Try it live

A self-contained vanilla-JS demo runs in CodePen below — same SDK, all 55 default catalog rows, two live fetches against data.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 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)
Bundle 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), the workmanager package (Flutter), or node-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.
If you want stronger typing on the per-item shape, the Field reference below shows the JSON, ready to bind to a TypeScript interface, Swift 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 as client.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:
  1. cfg.catalogs in your SDK config — explicit, wins if set. Use this to mix trending + derived rows with custom transform closures (see below).
  2. 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.
  3. Auto-built fallback — if neither of the above is set, the SDK builds one row per supportedType × timeframes[0] from your CONFIG.
So with the JSON catalogs already populated, 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:
Need the rows synchronously (e.g. inside a render function)? 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.
Use size: 500 once you render derived rows. The top-100 file gives ~10-30 items per derived row at best (e.g. hiddenGems off today_100 movies typically yields 10-15 candidates). The top-500 file gives ~50-200 per row — enough to scroll horizontally. Same fetch schedule, ~3-5x larger file.
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.
Yields on the live top-500 (May 2026 snapshot) — pick recipes to match your row-fill target: 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

The SimklTrendingRecipes 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.
Tuning a recipe per catalog row. Every recipe accepts an options object as its second arg. In the JSON, set them via recipe_opts. For example, to make tv_hidden_gems stricter — only titles outside Simkl’s top 3000 with rating ≥ 8 instead of the default 2000/7.5 — override the knobs:
Same pattern works for any recipe — bestOfDecade with { "decade": 1980 }, bestOfNetwork with { "network": "AMC" }, highestRated with { "minVotes": 2000 }, etc. Check today’s live data before bumping thresholds aggressively though; over-strict opts can produce empty rows on smaller trending feeds.
Attribution still applies to derived rows. A row labeled “Most Watchlisted Movies” or “Hidden Gems” still needs to include “Simkl” or “Most Watched” to satisfy the attribution requirement — e.g. “Most Watchlisted on Simkl”, “Simkl Hidden Gems”, “Trending in Japan — Powered by Simkl”.

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

string
Display title.
object[]
Localized and alternate titles. Always present on every catalog, but often an empty array — check length before reading. Popular titles can carry dozens of entries (60+ on the most widely-released films), so slice before rendering.Each entry has three keys:
  • name (string) — the alternate title.
  • type (string)official for a localized release title, original for the title in the production’s own language.
  • lang (integer) — Simkl-internal language code. This is not an ISO 639 code and there is currently no public mapping table, so treat it as an opaque grouping key rather than something to render.
If you only need one display title per item, use title and ignore this field entirely.
string
Path on simkl.com (e.g. /movie/2533163/the-drama). Prepend https://simkl.com to deep-link.
string
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).
string
Image path fragment for fanart. Same prefixing rules as poster. When null, hide the fanart element — there’s no dedicated fanart placeholder.
object
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 and ID variants (letterslug, traktmslug, tvdbslug, trakttvslug, mdlslug, jwtv, …) may appear on items with those platform links — the response is permissive, so read the keys you need and ignore the rest.
integer
Simkl service rank for this media type.
integer
Number of users who watched this title in the timeframe.
integer
Number of users with this title on their Plan-to-Watch watchlist.
string
Percentage of users who started and dropped, formatted as a string (e.g. "1.6%").
object
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 }.
string
Original release date in MM/DD/YYYY format.
string
ISO-style country code (e.g. us, jp). Nullable on anime (~28% of the anime feed); always populated on movies, TV, and DVD.
string | null
Lowercase two-letter language code for the production’s original audio (en, ja, zh, ko). Nullable on anime (~33% of the anime feed); always populated on movies, TV, and DVD. Distinct from country, which is the country of origin — a Japanese-language title can be country: "us" on a co-production.
string
Human-readable duration (e.g. 1h 45m, 25m).
string
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.
string[]
Array of genre tags.
string
YouTube video ID.
string
Synopsis.
string
Pre-formatted human-readable summary line (release year, budget, box office, network, etc.).
string
Movies only. DVD release date (MM/DD/YYYY).
string
Movies only. Theatrical release date (MM/DD/YYYY).
string | null
Anime only. Romanized Japanese title (e.g. Mushoku Tensei III: Isekai Ittara Honki Dasu), for apps that display romaji alongside or instead of the English title. null on roughly a quarter of the anime feed — fall back to title.
string
Anime only. One of tv, movie, special, ova, ona, music video.
integer
TV / anime. Episode count.
string
TV / anime. Broadcasting network.

Without the SDK — raw URL and curl

The SimklTrendingClient 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

If you’d rather construct the URL in code:
  • <category> — omit for Combined (movies + tv + anime in one response); use movies, tv, or anime for type-specific.
  • <timeframe>today, week, or month.
  • <size>100 or 500.
DVD releases use a different prefix and no timeframe:

Fetch it

Always send a User-Agent with your app name and version (e.g. myapp/1.0) to avoid accidental blocking.