client_secret, no redirect URI, no browser on the device.
Any V2 client type can use this flow — Mobile, desktop & browser apps, TV, devices & command line, or Server apps & services. You do not have to register as a TV or device app to use the device flow.Pick TV, devices & command line when the device flow is the only flow your app will ever use: that type registers no redirect URI, which also means it cannot run the authorization code flow. If you have a web or mobile companion that signs users in through a browser, register Mobile, desktop & browser apps and use both flows from the one app.Client type is fixed at registration and cannot be changed later, so this is worth a moment’s thought.
The flow
Ask for a device code
scope gives you media:read only. If your device writes anything — scrobbling, marking watched — ask for media:write explicitly.Get the user to the approval page
You get two forms back, and the complete one is better whenever you can use it:Show
Every character a user types is a chance to mistype it and a reason to abandon halfway, so reach for
verification_uri_complete first and fall back to the pair only when your device genuinely cannot get a URL in front of them.Ways to deliver the complete URL, by what your device can do:user_code exactly as returned, hyphen included, wherever you show it. It is not case-sensitive and the hyphen is part of the format.The complete URL pre-fills the code. It does not approve anything. The user still lands on the consent screen and still has to click Allow, so it is safe to put in an email, a log line or a chat message — it grants nothing on its own, and it is useless to anyone who is not signed in to the account they want to connect.
device_code is the opposite: it is the credential your device polls with. Never display it, never log it, never put it in a URL.Poll for approval
Poll Until the user approves, every poll returns an
POST /oauth2/token every interval seconds — 5 by default:error telling you what to do next. See the table below.Store the tokens
Once approved you get the standard token response — access token valid 7 days, refresh token valid 180 days. From here the device behaves like any other client.Check the
scope in that response before you rely on it. An unrecognised scope string is silently downgraded to read-only rather than rejected, so a typo in step 1 produces a token that works until your first write. See Scopes.Poll responses
The deadline branch is not optional. Because a declining user producesauthorization_pending forever, that check is the only thing that ends the loop when someone says no. Without it your device polls until the code expires.
Note the two different status codes below. Everything in the first group is a normal part of polling; the second is a configuration problem that polling will never fix.
The
401 is raised during client authentication, before your device_code is even looked at, so it tells you nothing about the code itself.
Two things RFC 8628 leads you to expect that do not happen here
authorization_pending covers two different situations. It means “not approved yet”, and it also means “approved, but another poll is currently claiming it” — which can happen briefly if your device has more than one poll in flight.You cannot tell them apart, and you do not need to. The correct response to both is to keep polling.Code entry is more forgiving than it looks
Theuser_code is 8 characters from a restricted alphabet, displayed as XXXX-YYYY. When the user types it, Simkl normalises the input before matching:
- Case is ignored —
bdwp-hqpkworks. - Hyphens and spaces are optional —
BDWPHQPKandBDWP HQPKboth work. IandLare read as1, andOas0— so the classic misreadings succeed rather than failing.
Lifetimes
Fifteen minutes is generous for someone with their phone in hand and tight for someone who wandered off. Show a countdown if you have the screen space, and make starting over easy.
See also
POST /oauth2/device
Endpoint reference for the initial request.
POST /oauth2/token
The polling endpoint, and the other two grants.
Tokens and refresh
Keeping a device signed in without re-prompting.
Migrating from V1
Moving a device integration across from the older PIN flow.