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.Show the code to the user
Display
user_code exactly as returned, hyphen included, and point the user at verification_uri.If you can render a QR code, encode verification_uri_complete instead β it pre-fills the code, so the user types nothing at all. That is a meaningful difference on a TV.Keep device_code secret. It is the credential your device polls with, and it should never appear on screen.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.