Skip to main content
A V2 client_id cannot use this flow. It is refused with 400 unauthorized_client and a message naming the V2 endpoint to use instead. A registration is V1 or V2, never both — see AUTH V2.
This is the AUTH V1 flow, and V1 is being retired — apps on it are expected to stop working around April 2027. It still works today, but new integrations should use AUTH V2 and existing ones should plan to move. The V2 equivalent of this page is the authorization code flow; the path across is Migrating from V1 to V2.
There’s no reinventing the wheel here — the API uses OAuth 2.0. Requesting user-associated information requires a token that needs to be included in all request headers made to the API. To obtain the client_id and client_secret, please create an app first.
Two domains, two roles. OAuth uses two different hosts — easy to mix up, and the most common cause of “404 Not Found” during integration:If your authorize URL points at api.simkl.com you’ll get a 404 — it has to be simkl.com.
To make calls on behalf of a user you have to obtain an access_token. To do this, first send the user to https://simkl.com/oauth/authorize to receive a code, then post it in JSON format to https://api.simkl.com/oauth/token. The response contains the access_token.

STEP 1 — Authorize (simkl.com)

Open a URL like the one below in the user’s browser or a Custom Tab (do not use a WebView on mobile):
Simkl will redirect back to your redirect_uri with ?code=... appended (and &state=... if you sent one).
Build this URL against https://simkl.com — not https://api.simkl.com. The API host has no /oauth/authorize page.
Use state for CSRF protection. Generate a random string before redirecting and store it on your session. When the redirect arrives, verify the state echoed back matches what you sent. If it doesn’t, reject the request — someone else may have started a flow on your behalf.
User denial doesn’t return an error= parameter. If the user clicks “No” on the consent screen, Simkl redirects to / on simkl.com — not back to your redirect_uri with error=access_denied. Treat any flow where the redirect never lands within a sensible timeout (e.g. 5 minutes) as a denial / cancellation and let the user retry.

STEP 2 — Exchange code for token (api.simkl.com)

POST the code to https://api.simkl.com/oauth/token with your client_id, client_secret, redirect_uri, and grant_type=authorization_code.
Both content-types and both credential locations work. Simkl’s POST /oauth/token accepts:
  • Content-Type: application/x-www-form-urlencoded (the RFC 6749 §3.2 default) or Content-Type: application/json — pick whichever your HTTP client prefers
  • Client credentials in the request body (client_id + client_secret parameters) or in the Authorization: Basic header (RFC 6749 §2.3.1) — both paths are honored
That means off-the-shelf OAuth libraries work out-of-the-box with no custom encoding or auth-method config. The two equivalent ways to call the token endpoint:
For library-specific examples (Python, Node, Java, Go, PHP), see OAuth client libraries — most are zero-config.
The successful response contains your access_token:
expires_in is 5 years in seconds — effectively infinite for any realistic session. There’s no refresh_token; Simkl tokens are long-lived and the API has no refresh-token grant. If a 401 arrives before that lifetime elapses, the user revoked your app at Connected Apps settings — restart the flow from STEP 1.
The authorization code is single-use. As soon as you POST it to /oauth/token, the server deletes it — even if the exchange fails (network error, validation mismatch, etc.). If your exchange fails for any reason, don’t retry with the same code — restart from STEP 1.
Same user, same token. If the same user runs the OAuth flow twice for your app — whether via the standard flow, PKCE, or PIN — Simkl returns the same access_token both times (it increments an internal usage counter but doesn’t rotate the token). Storing the latest response is safe; you don’t need to invalidate older tokens of your own because there aren’t multiple ones.
Public clients (mobile, SPA, browser extensions, desktop binaries) must not embed client_secret. Anything compiled into the user’s app should be considered leaked. Use the Public PKCE flow (code_verifier + code_challenge) instead — same browser-based UX, no secret required.

See also

Set up authentication

The V2 equivalent of this page’s setup: client type, secret, flow and scope.

Public PKCE

The variant for mobile, SPA, browser extensions, and desktop binaries — same browser UX, no client_secret required.

PIN flow

The alternative for TVs, consoles, watches, CLIs, and media-server plugins — no client_secret and no redirect required.

GET /oauth/authorize

Endpoint reference — every accepted query parameter, including PKCE.

POST /oauth/token

Endpoint reference — body fields, response shape, error codes, interactive playground.