> ## 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.

# Rate limits

> How Simkl applies request limits and what to do when you hit one.

<Note>
  ### 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`](/api-reference/simkl/get-all-items) without gating behind [`/sync/activities`](/api-reference/simkl/get-activities). The patterns on this page keep your app fast **and** keep the API healthy for every other developer building on Simkl.
</Note>

## 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** |

On overage: `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](/api-reference/trending)                                                                                                                | Static JSON on `data.simkl.in` — Cloudflare-cached, no per-user state.                |
| [Calendar data files](/api-reference/calendar)                                                                                                                | Same — static JSON, Cloudflare-cached.                                                |
| [`GET /movies/{id}`](/api-reference/simkl/get-movie), [`GET /tv/{id}`](/api-reference/simkl/get-tv-show), [`GET /anime/{id}`](/api-reference/simkl/get-anime) | Cloudflare-cached by Simkl ID — repeat lookups of popular titles cost almost nothing. |
| [`GET /tv/episodes/{id}`](/api-reference/simkl/get-tv-episodes), [`GET /anime/episodes/{id}`](/api-reference/simkl/get-anime-episodes)                        | Same — Cloudflare-cached episode-list lookups.                                        |

**Everything else stays sequential.** [Sync endpoints](/api-reference/simkl/get-all-items), [user-state endpoints](/api-reference/simkl/get-user-settings), and [search endpoints](/api-reference/simkl/search-by-text) serve per-user or dynamic data that bypasses the edge cache — every parallel call hits the origin directly and counts individually against the cap.

### Why parallel on uncached endpoints hurts

**It doesn't increase throughput.** The ceiling is 10 GET/sec, 1 POST/sec. Ten parallel GETs at `t=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 `429`s.

**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](/api-reference/simkl/get-all-items): apps that hammer the API — particularly write endpoints or polling without [`/sync/activities`](/api-reference/simkl/get-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`](/api-reference/simkl/get-activities) so most `/sync/all-items` calls never happen.

<Note>
  **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.
</Note>

## 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](/api-rules#5-free-for-non-commercial-use-and-under-150-month). |

## Best practices

<AccordionGroup>
  <Accordion title="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](/conventions/images).
  </Accordion>

  <Accordion title="Use the CDN files for trending and calendars">
    The [Trending](/api-reference/trending) and [Calendar](/api-reference/calendar) JSON files don't count against your `client_id` quota and are cached at the edge. Always prefer them over per-user catalog calls.
  </Accordion>

  <Accordion title="Sync incrementally">
    Always check [`/sync/activities`](/api-reference/simkl/get-activities) before pulling watchlists. Skip watchlists whose timestamp didn't change. See the [Sync guide](/guides/sync) for the two-phase model.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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`](/api-reference/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}`](/api-reference/simkl/get-movie) / [`GET /tv/{id}`](/api-reference/simkl/get-tv-show) / [`GET /anime/{id}`](/api-reference/simkl/get-anime) call is Cloudflare-cached. Much cheaper than `GET /search/id`.
  </Accordion>
</AccordionGroup>

## Hide your client\_id in CI

If you're using a CI system, store `client_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.
