Token (V2)
Exchanges a grant for an access token. One endpoint serves all three AUTH V2 grants, selected by grant_type:
grant_type | Use it to |
|---|---|
authorization_code | Turn a code from the consent redirect into tokens. |
refresh_token | Get a fresh access token without involving the user. |
urn:ietf:params:oauth:grant-type:device_code | Poll for approval during the device flow. |
This endpoint is on api.simkl.com, while the consent page is on simkl.com. Rather than hardcoding both, a library that reads RFC 8414 metadata can take the single issuer setting https://simkl.com and resolve token_endpoint itself — the same document also advertises all three grants above. See Discovery.
Client authentication
Apps registered as Server apps & services must authenticate. Send the credentials either as an Authorization: Basic header (RFC 6749 section 2.3.1) or as client_id and client_secret parameters in the body. Both are accepted.
Mobile, desktop & browser apps and TV, devices & command line registrations have no secret and send only client_id.
Client authentication is checked before the grant is looked at, and a failure is 401 invalid_client, not 400. If a server app omits its secret it gets invalid_client no matter what else is wrong with the request, which can mask a second problem behind it.
Body format
Both application/x-www-form-urlencoded (the RFC 6749 section 3.2 default) and application/json are accepted. Every parameter must be a plain string. Sending an array or object for any parameter returns 400 invalid_request naming the offending parameter.
Authorization code grant
Requires code, code_verifier and the same redirect_uri you sent to the authorize endpoint.
The code is consumed the moment it reaches this endpoint, before any validation runs. A wrong code_verifier, a mismatched redirect_uri or a foreign client_id all destroy the code. You cannot correct the request and retry with the same code — the user has to authorize again. Each successful exchange creates a new, independent grant; authorizing a second time does not revoke the first.
Refresh token grant
Requires refresh_token. The same refresh token is returned, with its 180-day window slid forward. The previous access token stops working immediately, so a single grant supports exactly one live access token — two processes sharing one refresh token will keep cutting each other off. Give them separate authorizations.
A scope narrower than the original grant is ignored rather than applied; a wider one is rejected with invalid_scope. A grant’s scope can never shrink.
Device code grant
Requires device_code. Poll no faster than the interval returned by POST /oauth2/device.
authorization_pending— keep polling at the same interval. This is also what you get if the user declined, because declining writes nothing; your own timeout atexpires_inis the only signal you will get.slow_down— add 5 seconds to your interval and wait. The poll timer is reset by the attempt itself, so retrying immediately keeps you permanently rate-limited.expired_token— the code is dead. Start a new device authorization.
Every /oauth2/* response is sent with Cache-Control: no-store and Pragma: no-cache. Do not cache these responses, including the errors.
Body
Parameters for POST /oauth2/token. Which ones apply depends on grant_type; every value must be a plain string.
Selects the grant.
authorization_code, refresh_token, urn:ietf:params:oauth:grant-type:device_code Your app's client ID.
Apps registered as Server apps & services only. Only needed if you are not using Authorization: Basic. Never ship this inside an app users install.
authorization_code grant. Single-use, and consumed even by a failed exchange.
authorization_code grant. 43 to 128 characters from A-Z a-z 0-9 - . _ ~. A malformed verifier is reported as invalid_grant, the same error as a genuine mismatch.
authorization_code grant. Must match the one used to obtain the code.
refresh_token grant.
Device grant. From POST /oauth2/device.
refresh_token grant only, and rarely useful: a narrower scope is ignored and a wider one is rejected.
Response
Tokens issued.
RFC 6749 section 5.1 token response. Identical in shape for all three grants.
Bearer token, 43 characters, prefixed simkl_at_. Send as Authorization: Bearer ... on every request.
Always the capitalised string Bearer. AUTH V1 returns lowercase bearer, so compare case-insensitively if you support both.
Bearer Seconds until the access token expires. Always 604800, i.e. 7 days.
604800
43 characters, prefixed simkl_rt_. Valid for 180 days, and the window slides forward each time you use it. Non-rotating: the same string is returned every refresh, so store it once.
The scope actually granted. Always exactly one of these two values, whatever you asked for. Check it rather than assuming your request was honoured.
media:read, media:read media:write