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.

This flow is designed for devices with limited input — media-center plugins, game consoles, smartwatches, smart TVs, command-line tools, system services. Your app shows a short alphanumeric code; the user enters it on their phone or computer; the device polls until they approve. No client_secret and no redirect URI required. After the user authorizes, the device receives an access_token and behaves identically to an OAuth client from that point on.

Steps

Request a device code

GET /oauth/pin?client_id=… returns:
{
  "result":           "OK",
  "device_code":      "DEVICE_CODE",
  "user_code":        "ABCDE",
  "verification_uri": "https://simkl.com/pin",
  "verification_url": "https://simkl.com/pin",
  "expires_in":       900,
  "interval":         5
}
Show user_code to the user. expires_in is 15 minutes; interval is 5 seconds (your polling cadence).
device_code is returned as the literal string "DEVICE_CODE" — it’s a placeholder field kept for OAuth 2.0 Device Authorization Grant response-shape compatibility. Clients only need user_code. You can ignore device_code entirely.
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.

Display the code and instructions

Tell the user: “Go to simkl.com/pin and enter ABCDE.” Render the code in a large, easy-to-read style — it’s typed by hand on a phone.

Poll for the result

GET /oauth/pin/{USER_CODE}?client_id=… every interval seconds. Two response shapes:
// Still pending — keep polling
{ "result": "KO", "message": "Authorization pending" }

// User approved — stop polling, store access_token
{ "result": "OK", "access_token": "..." }
Respect the returned interval (5 seconds). Polling faster won’t help — the user enters their PIN at human speed. Once expires_in (15 minutes) elapses, the user_code is dead; request a fresh one and restart.
Stop polling as soon as you receive the access_token. After successful authorization the server deletes the code; if you keep polling on the deleted (or any unknown) user_code, this endpoint falls through to the create-a-new-code branch and you’ll get back the same shape as GET /oauth/pin — including a brand-new user_code different from the one you polled. Detect any response containing device_code as “the original code is gone” and stop.

Store the token and stop polling

Save the access_token securely. From here on, the device works like any OAuth client — send Authorization: Bearer <access_token> on every authenticated request. Tokens are long-lived — the token-mint response advertises expires_in: 157680000 (about 5 years), and there’s no refresh-token grant. They only stop working when the user revokes your app from Connected Apps settings; on the next 401, restart the PIN flow.

Why PIN vs OAuth?

PINOAuth 2.0
Best forTVs, consoles, watches, CLI tools, media-server plugins, IoTMobile apps, web apps, desktop apps
Needs client_secretNoYes (or PKCE for public clients)
Needs redirect_uriNoYes (or PKCE-only with no registered redirect)
User experienceApp shows code → user types it on phoneTap login → browser → approve → back to app
Time to token30 seconds – 2 minutes~5 seconds
See Choose a flow for the full per-platform comparison and code samples.

See also

Choose a flow

Platform-by-platform recommendations, side-by-side comparison, common pitfalls.

OAuth 2.0 flow

The alternative for browsers, mobile, and desktop — token in ~5 seconds.

GET /oauth/pin

Endpoint reference — request a user_code.

GET /oauth/pin/{USER_CODE}

Endpoint reference — poll for the access token.