Skip to main content

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.

Simkl uses standard HTTP status codes. Error responses always have a JSON body with error (machine-readable identifier), code (integer — the HTTP status code echoed in the body), and an optional message (human-readable guidance).
Standard error envelope
{
  "error":   "client_id_failed",
  "code":    412,
  "message": "Your client_id is wrong. Try another one"
}
Branch on error, never on message. The error identifier is the contract — it’s stable across releases. The message text is developer-facing prose that may be reworded at any time. Parsing message will break your client.

At a glance

CodeNameRetry?
200OK
201Created
204No Content
302Found
400Bad RequestNo — fix the request
401UnauthorizedNo — re-authenticate
403ForbiddenNo — fix the app or contact us
404Not FoundNo
409ConflictNo
412client_id failedNo
429Too Many RequestsYes — back off
500Internal Server ErrorYes — exponential backoff
502Bad GatewayYes — exponential backoff
503Service UnavailableYes — exponential backoff
Not every endpoint can return every code. Each endpoint’s reference page lists the codes that actually fire there — many endpoints can only return a subset (e.g., /sync/activities can only return 200, 401, 412, 429, 500). The 4xx/5xx pages below describe the codes; check the per-endpoint reference for which apply.

Success codes

OK

200 — Success. The body contains the resource you requested.

Created

201 — Success. A new resource was created. Typical for POST /sync/... and POST /scrobble/....

No Content

204 — Success but the response body is empty. Typical for DELETE endpoints.

Found

302 — A redirect. Follow the Location header. Most commonly returned by the redirect endpoint.

4xx — your request

Bad Request

400 — The request was malformed. The message field usually identifies the offending parameter. Real error values returned
errorWhenExample message
empty_fieldA required field is missingMissed "to" parameter
wrong_parameterA field has an unaccepted valueWrong "to" parameter
Fix Read message, correct the request, then resend. Don’t retry without changing the request.
400 Bad Request
{
  "error":   "empty_field",
  "code":    400,
  "message": "Missed \"to\" parameter"
}

Unauthorized

401 — Missing or invalid user access token. Common causes
  • Authorization header missing on a token-required endpoint.
  • The user’s access_token was revoked at Connected Apps settings.
  • A typo or truncation in the token value.
Fix Re-authenticate with OAuth 2.0 or PIN. Simkl tokens advertise expires_in: 157680000 (5 years) on mint, so a 401 in practice means the user removed your app from their account or the token never matched in the first place.
401 Unauthorized
{
  "error": "user_token_failed",
  "code":  401
}
The 401 response also carries an RFC 6750 §3 WWW-Authenticate header for clients that key off it:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer realm="api.simkl.com", error="user_token_failed"

Forbidden

403 — Refused. The request was understood but the caller is not permitted to perform it. Known error values
errorWhen
redirect_failedOAuth/PIN redirect parameter doesn’t match a URL registered for your app
forbiddenGeneric refusal — feature gated to Simkl Pro/VIP, or an action the user hasn’t consented to
Fix Match the redirect URL exactly to what’s registered in your developer settings. For Pro/VIP-gated features, surface an upgrade prompt rather than retrying.
403 Forbidden — redirect mismatch
{
  "error":   "redirect_failed",
  "code":    403,
  "message": "Invalid redirect_uri. This value must match a URL registered with the API Key."
}

Not Found

404 — The URL or resource doesn’t exist.
Most catalog endpoints don’t return 404 for missing IDs. Hitting /movies/99999999 returns 200 [] (empty array), not 404. Use 404 documentation in per-endpoint references as the source of truth — most read-only catalog endpoints simply return empty.
404 Not Found
{
  "error": "id_err",
  "code":  404
}

Conflict

409 — The resource state conflicts with the request. Common causes
  • Tried to POST /scrobble/stop on a session that completed in the last hour.
  • Tried to perform a write that would duplicate an existing record.
Fix Treat 409 as soft-success when scrobbling — the user already finished the episode. Don’t retry the same call. The body includes watched_at (when the original watch landed) and expires_at (when the 1-hour duplicate-window closes), so you can show the user the existing watch state instead of re-firing the call.
409 Conflict (POST /scrobble/stop)
{
  "error":      "already_watched",
  "watched_at": "2026-05-08T14:00:00Z",
  "expires_at": "2026-05-08T15:00:00Z"
}

client_id failed

412 — Your client_id is missing, wrong, suspended, or has hit a request limit. Common causes
  • Typo in client_id (use the value from developer settings, not a screenshot).
  • App was suspended for a Terms of Service violation.
  • App hit a per-client_id request cap — see Rate limits.
Fix Double-check the value. If it’s correct, reach out on Discord.
412 client_id failed
{
  "error":   "client_id_failed",
  "code":    412,
  "message": "Your client_id is wrong. Try another one"
}

Too Many Requests

429 — Rate limit hit. Fix Implement exponential backoff. Cache responses aggressively. Use Trending and Calendar CDN files instead of polling. Full guidance: Rate limits.
429 Too Many Requests
{
  "error": "rate_limit",
  "code":  429
}

5xx — our problem

Internal Server Error

500 — Something broke on our side. Fix Retry with exponential backoff. If the error persists for more than ~30 seconds, report it on Discord.
500 Internal Server Error
{
  "error": "internal",
  "code":  500
}

Bad Gateway

502 — Simkl is being upgraded or briefly unreachable. Fix Retry with exponential backoff. Check status for known issues.

Service Unavailable

503 — Servers are up but overloaded. Fix Retry after the delay. Respect any Retry-After header.

Handling errors gracefully

Exponential backoff

Recommended retry pattern for transient errors (429, 500, 502, 503):
AttemptWait before retry
11 s
22 s
34 s
48 s
516 s (give up after this)
Add jitter (random 0–1 s) so multiple clients don’t synchronize.
retry-with-backoff.js
async function retry(fn, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === attempts - 1 || ![429, 500, 502, 503].includes(err.status)) throw err;
      const delay = Math.min(1000 * 2 ** i, 60000) + Math.random() * 1000;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

Don’t retry deterministic errors

400, 401, 403, 404, 409, 412 mean something is wrong with the request itself. Retrying without changing the request just wastes quota and triggers 429.

User-facing messaging

message is human-readable but written for developers. For end users:
CodeSuggested user message
401Please sign in again to continue.
403This action isn’t available for your account.
404We couldn’t find that title.
429Slow down — try again in a moment.
5xxSimkl is having a moment. We’ll retry automatically.

Logging

Always log error and code together with your request URL. error is the stable contract you’ll branch on; code confirms the HTTP class.