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 device flow, and V1 is being retired — apps on it are expected to stop working around April 2027. New integrations should use the AUTH V2 device flow, which implements RFC 8628 and works with stock OAuth libraries, and existing ones should plan to move via Migrating from V1 to V2. The two flows are not interchangeable — a V1 app cannot call the V2 endpoints, and a V2 app cannot use this one.
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:
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:
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?

See Set up authentication for the V2 setup path, including the device flow that replaces this one.

See also

Set up authentication

The V2 setup path, including the device flow that replaces this one.

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.