Documentation Index
Fetch the complete documentation index at: https://api.simkl.org/llms.txt
Use this file to discover all available pages before exploring further.
Help us keep the Simkl API at 100% uptime — optimise your app’s requests
The limits below are generous and most apps never hit them. The ones that do are almost always parallelizing calls to endpoints that should be sequential, or polling/sync/all-items without gating behind /sync/activities. The patterns on this page keep your app fast and keep the API healthy for every other developer building on Simkl.Request limits
Limits are applied in two scopes — pick the bucket that matches your call:| Scope | Applies to | Limit |
|---|---|---|
Per client_id (app-wide) | Unauthenticated requests using only client_id (catalog reads, search, trending, calendar) | 10 GET/sec, 1 POST/sec |
| Per user | Authenticated requests with Authorization: Bearer ACCESS_TOKEN (sync, scrobble, ratings, user settings) | 10 GET/sec, 1 POST/sec per access token |
GET returns 429 Too Many Requests — back off and retry. POST triggers a temporary throttling block on the offending client_id or token; repeated overages extend the block.
The two buckets are tracked separately, so heavy authenticated use by one user doesn’t burn down the app-wide unauthenticated quota. But within a single user’s session, parallel calls to /sync/... still collide against that user’s own per-token cap.
Parallel requests — when allowed
Do not parallelize requests unless necessary. A single sequential client is the default — it stays well under the limits and never triggers a throttle. Parallel requests are explicitly allowed on endpoints cached at the Cloudflare edge, because parallel hits there are served from cache and don’t pressure Simkl’s origin:| Endpoint family | Why parallel is fine |
|---|---|
| Trending data files | Static JSON on data.simkl.in — Cloudflare-cached, no per-user state. |
| Calendar data files | Same — static JSON, Cloudflare-cached. |
GET /movies/{id}, GET /tv/{id}, GET /anime/{id} | Cloudflare-cached by Simkl ID — repeat lookups of popular titles cost almost nothing. |
GET /tv/episodes/{id}, GET /anime/episodes/{id} | Same — Cloudflare-cached episode-list lookups. |
Why parallel on uncached endpoints hurts
It doesn’t increase throughput. The ceiling is 10 GET/sec, 1 POST/sec. Ten parallel GETs att=0 land in the same 1-second window — you’ve spent the whole budget at once, and request 11 gets 429. The same ten calls sent sequentially over one second land you in the exact same place with zero 429s.
Servers are commodity hardware, not supercomputers. Imagine opening 10 copies of Photoshop on your laptop at the same time — each one loads the full app into RAM independently, the fans spin up, and everything else slows to a crawl. A web server handling 10 parallel uncached requests does the same thing: each request loads its own slice of the application — framework boot, autoloader, config, model code, request context — and holds it until the response is sent. Ten parallel requests means 10 full copies of that boot state in RAM at once, plus 10 database connections, 10 worker threads, 10 response buffers, and 10 fan-out calls to downstream services (read replicas, search index, image proxy). Connection pools, CPU schedulers, GC pauses, and disk I/O all have finite headroom; when a burst exceeds capacity, in-flight requests slow down or fail and the next caller waits for the queue to drain. Sequential traffic reuses the same worker over and over — boot cost paid once per worker, not once per request — so no single resource ever spikes above safe levels.
A 429 has user-visible cost. A throttled call breaks the user’s session — the page stalls, the sync stalls, a mid-flight write fails. Your client then has to back off and retry, doubling the time-to-success. Retry storms also re-pressurize the origin and can extend the throttle window. “Just parallelize and let the API tell us to slow down” is not a viable strategy; the failed requests have already cost user-visible time.
Sustained overage suspends your client_id. Per the /sync/all-items warning: apps that hammer the API — particularly write endpoints or polling without /sync/activities gating — get their client_id suspended. No warning, no appeal.
Burst patterns look like abuse. Edge and origin monitoring can’t tell a well-meaning client firing 50 parallel sync calls apart from a botnet or scraper — the signature is the same: one client_id or IP sending many concurrent requests to non-cacheable URLs in a short window. Automated mitigation kicks in (temporary block, IP throttle, longer back-off) without anyone making a judgment call about intent.
The correct pattern for uncached endpoints: one in-flight request at a time per token; batch into one call instead of N parallel calls whenever the endpoint accepts arrays (every write endpoint does — see Batch writes below); gate polling behind /sync/activities so most /sync/all-items calls never happen.
Cache invalidation on the cached endpoints is automatic. When Simkl updates the underlying record (admin edits, metadata refresh, image swap, new episode airing, etc.), the corresponding Cloudflare cache entry is purged server-side. The next call returns fresh data — no TTL to wait out, no
?nocache=… trick needed. Same applies to the trending and calendar JSON files. Your own app-level cache, if you have one, still has to be invalidated by your client.Limit signals
| Status | Meaning |
|---|---|
429 Too Many Requests | You’ve sent too many requests too quickly. Back off and retry with exponential backoff. |
412 client_id_failed | You’ve exceeded the total request limit for your client_id, or your throttling block is still active. Re-evaluate your usage or reach out about a commercial license. |
Best practices
Cache aggressively
Cache aggressively
Most metadata never changes. Cache responses by URL on the device for the longest sensible time — minutes for user data, hours for catalog data, indefinitely for images.
Use the CDN files for trending and calendars
Use the CDN files for trending and calendars
Sync incrementally
Sync incrementally
Always check
/sync/activities before pulling watchlists. Skip watchlists whose timestamp didn’t change. See the Sync guide for the two-phase model.Batch writes — one POST per second is enough for arrays of 50+ items
Batch writes — one POST per second is enough for arrays of 50+ items
Every write endpoint accepts arrays.
POST /sync/history, POST /sync/history/remove, POST /sync/ratings, and POST /sync/add-to-list will happily process 50 items in one request. Sending 50 separate single-item POSTs immediately blows past the 1-POST-per-second cap and gets your client_id throttled — sending one batched array stays well under.Retry transient errors with exponential backoff
Retry transient errors with exponential backoff
On
429, 500, 502, 503: wait, retry, double the wait, cap it. Give up after 5 attempts. A common starting pattern is 1s → 2s → 4s → 8s → 16s, but tune to your application’s tolerance.Resolve external IDs via /redirect rather than search
Resolve external IDs via /redirect rather than search
If you have an IMDb / TMDB / MAL / etc. ID and need the matching Simkl ID, use
GET /redirect — it returns a tiny 301 redirect with the Simkl ID in the Location header (no JSON body), and the follow-up GET /movies/{id} / GET /tv/{id} / GET /anime/{id} call is Cloudflare-cached. Much cheaper than GET /search/id.Hide your client_id in CI
If you’re using a CI system, storeclient_id and client_secret in your provider’s encrypted secrets / environment variables — not in repository files. A leaked credential burns your quota and is hard to recover.