Every API call that touches user data needs a userDocumentation Index
Fetch the complete documentation index at: https://api.simkl.org/llms.txt
Use this file to discover all available pages before exploring further.
access_token. Simkl gives you three ways to get one — they’re all variants of OAuth 2.0 from the user’s perspective, but the integration story is very different. Pick the one that matches the device your app runs on; once you have a token, the rest of the API is identical.
OAuth 2.0
For server-side web apps that can keep a
client_secret. The user logs in via their browser; your backend exchanges the code for a token.Public PKCE
For mobile, SPA, browser extensions, desktop binaries — any client where you can’t safely embed
client_secret. Same browser-based UX, no secret required.PIN
For TVs, consoles, watches, CLIs, and media-server plugins. Show a 5-character code; the user enters it on their phone.
Find your platform
- Mobile
- Web
- Desktop
- TV & console
- Headless / plugin
| Platform | Use | Recommended UI |
|---|---|---|
| iOS / iPadOS | OAuth 2.0 | ASWebAuthenticationSession (iOS 12+) |
| Android | OAuth 2.0 | Chrome Custom Tabs (androidx.browser) |
| React Native | OAuth 2.0 | expo-web-browser or react-native-app-auth |
| Flutter | OAuth 2.0 | flutter_web_auth_2 |
| Capacitor / Ionic | OAuth 2.0 | @capacitor/browser |
| watchOS / Wear OS | PIN | The watch shows the code; the user enters it on their phone |
Don’t use an embedded
WebView for OAuth on mobile. Identity providers used by Simkl’s login page — Google sign-in, email auth, and others — refuse to render inside embedded WebViews (Android WebView, iOS WKWebView). Users hit a blank screen or a “this browser is not supported” error and can’t log in.Use the platform’s secure web-auth session instead — it runs in the user’s real browser context, shares their existing login cookies, and is accepted by every provider:- iOS / iPadOS →
ASWebAuthenticationSession. Mandatory — the App Store rejects the olderSFAuthenticationSession. On iOS 17.4+, prefer the newer initializer that handles universal links cleanly. - Android → Chrome Custom Tabs (
androidx.browser:browser). For Chrome-only apps you can opt into the newer purpose-built Auth Tab — Custom Tabs remains the broadest-compatibility default.
At-a-glance comparison
| OAuth 2.0 | Public PKCE | PIN | |
|---|---|---|---|
| User experience | Tap login → browser → approve → back to app | Same as OAuth 2.0 | App shows code → user types it at simkl.com/pin → app continues |
| HTTP calls | 1 redirect + 1 token POST | 1 redirect + 1 token POST | 1 code request + N polls |
| Time to token | ~5 seconds | ~5 seconds | 30 seconds – 2 minutes |
Needs client_secret | Yes | No — uses code_verifier + code_challenge | No |
Needs redirect_uri | Yes (pre-registered, byte-for-byte) | Yes, OR omit entirely if no redirect URI is registered (consent completes on simkl.com) | No |
| Code TTL | Short-lived — exchange immediately | Short-lived — exchange immediately | 15 minutes (expires_in: 900) |
| Best for | Server-side web apps | Mobile, SPA, browser extensions, desktop binaries | TVs, consoles, watches, CLIs, plugins |
How the OAuth flow works
Send the user to Simkl
Open
https://simkl.com/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&app-name=my-app-name&app-version=1.0 in the user’s system browser (or a Custom Tab / ASWebAuthenticationSession on mobile).Public clients should also send a PKCE code_challenge (and optional code_challenge_method, default S256) — this lets you skip client_secret.User approves
Simkl shows a consent screen. After approval, Simkl redirects to your
redirect_uri with ?code=AUTHORIZATION_CODE appended (and &state=… if you sent one).Exchange the code for a token
POST /oauth/token with the code, your client_id, and either:client_secret+redirect_uri(confidential clients), orcode_verifier(PKCE — public clients, no secret required).
access_token.Both content-types and both credential locations work. Simkl’s For library-specific examples (Python, Node, Java, Go, PHP), see OAuth client libraries — most are zero-config.
POST /oauth/token accepts:Content-Type: application/x-www-form-urlencoded(the RFC 6749 §3.2 default) orContent-Type: application/json— pick whichever your HTTP client prefers- Client credentials in the request body (
client_id+client_secretparameters) or in theAuthorization: Basicheader (RFC 6749 §2.3.1) — both paths are honored
Store and reuse
Save the
access_token securely. It’s long-lived and only stops working when the user revokes your app from Connected Apps settings. Send it as Authorization: Bearer … on every authenticated request.OAuth code samples
PKCE for public clients
If your app can’t safely keep aclient_secret — mobile, SPA, browser extension, desktop binary — use PKCE instead of the confidential flow above. The user experience is identical (browser-based OAuth); the difference is replacing the secret with a one-time code_verifier + code_challenge pair the client generates locally.
Public PKCE — full walkthrough
Step-by-step PKCE flow (RFC 7636), per-platform recipes (iOS / Android / Web SPA / Desktop Python), common pitfalls (verifier mismatch, code expiry, redirect URI byte-for-byte rules, multi-flow verifier storage), and the “no registered redirect URI” mode Simkl supports for PKCE.
How the PIN flow works
Request a code
GET /oauth/pin?client_id=…. The response contains:user_code is the 5-character code you show to the user. expires_in is 900 seconds (15 min). interval is 5 seconds — your polling cadence.The
device_code field is returned as the literal string "DEVICE_CODE" (not a per-request device code value). It’s a placeholder for compatibility with the OAuth 2.0 Device Authorization Grant response shape. Clients only need to remember user_code — that’s what you poll on and what the user enters.The response also includes a
verification_url key with the same value, kept as an alias. Read verification_uri — that’s the RFC 8628 §3.2 spelling.Show the code on screen
Display
user_code prominently. Tell the user: “Go to simkl.com/pin on your phone and enter ABCDE.”Poll while the user authorizes
GET /oauth/pin/{USER_CODE}?client_id=… every interval seconds. There are two response shapes:Stop polling as soon as you receive the
access_token. After a successful authorization the server deletes the code, and any subsequent poll on the deleted (or any unknown) user_code falls through to the create-a-new-code branch — you’ll get back the same shape as GET /oauth/pin with a brand-new user_code. Detect any response containing device_code as “the original code is gone” and stop.PIN code samples
After you have a token
Public endpoints (Search, Movies, TV, Anime, Ratings, Redirect) only need the required URL parameters. Endpoints that touch user data also need
Authorization.| Where | Value |
|---|---|
| URL params | client_id=YOUR_CLIENT_ID&app-name=my-app-name&app-version=1.0 |
Authorization header | Bearer YOUR_ACCESS_TOKEN (when token-required) |
User-Agent header | my-app-name/1.0 |
Content-Type header | application/json (for POST) |
Token lifecycle
How long does a token last?
How long does a token last?
The token-mint response carries
expires_in: 157680000 — 5 years in seconds. In practice tokens remain valid until the user revokes your app, so the lifetime advertised is more of a sentinel than a refresh hint (there’s no refresh-token grant — once expires_in does run out, the user has to re-consent through /oauth/authorize). Store one and reuse it until the user revokes — see “What happens when a user revokes access?” below.What happens when a user revokes access?
What happens when a user revokes access?
The user can revoke from Connected Apps settings. After revocation, every authenticated call returns
401 Unauthorized. Detect this and prompt the user to re-authorize.Where should I store the token?
Where should I store the token?
| Platform | Storage |
|---|---|
| iOS | Keychain |
| Android | EncryptedSharedPreferences or the Android Keystore |
| macOS / Windows / Linux | OS keychain (Keychain Access, Credential Manager, libsecret) |
| Web | httpOnly cookie set by your backend — never localStorage |
| CLI / server | A file with restrictive permissions, or a secrets manager |
What scope do tokens have?
What scope do tokens have?
All tokens currently return
scope: "public". There’s no granular permission system — every token grants every permission your app has been approved for.What if the same user logs in twice?
What if the same user logs in twice?
Simkl returns the same
access_token both times for a given (app, user) pair — whether the user re-runs the standard flow, PKCE, or the PIN flow. The server tracks how often the token has been issued (an internal usage counter) but doesn’t rotate the token itself. Storing the latest response is safe; you don’t need to invalidate older ones because there aren’t multiple ones. The token only stops working when the user revokes your app at Connected Apps settings.Common pitfalls
Don’t use an embedded
WebView for OAuth on mobile. Federated providers used by Simkl’s login page — Google sign-in, email auth, and others — refuse to render inside embedded WebViews. Users see a blank screen or a “browser not supported” error and can’t sign in. Use ASWebAuthenticationSession on iOS or Chrome Custom Tabs on Android — both run in the user’s real browser context and work with every provider.Pick a flow
OAuth 2.0
Confidential clients (server-side web).
client_id + client_secret + redirect_uri. Parameter reference and example responses.Public PKCE
Public clients (mobile, SPA, extensions, desktop).
client_id + code_verifier / code_challenge. Per-platform recipes.PIN
Browser-less devices (TVs, consoles, watches, CLIs). Show a code, poll until the user enters it.