Skip to main content
AUTH V2 has exactly two scopes. Because write implies read, there are only two meaningful states, and the token response says which one you got:
Why the examples ask for both, when write already implies read.Requesting scope=media:write on its own works and grants exactly the same access — you are not required to list both. We show media:read media:write for two practical reasons:
  • It matches what comes back. The token response always reports media:read media:write when write is granted. Asking for the same string you will be handed makes a request-versus-response comparison trivial, which matters because checking the returned scope is the defence against a silent downgrade.
  • It is what OAuth expects. scope is a space-delimited list of everything you want, so listing both is the conventional form and what a stock library will produce.
Either works. If you prefer the shorter form, use it — just compare against the response rather than against what you sent.

Which one do you need?

If your app only displays things, ask for media:read. If it scrobbles, marks items watched, rates, or changes lists, ask for media:write. Requesting write when you only read is not harmful, but it makes your consent screen ask for more than you need, and some users decline on that basis. Ask for what you use.

Requesting a scope

Pass it on the authorize URL, space-separated (so %20-encoded in a query string):
Or on the device request:

Two ways to silently get a read-only token

Both of these produce a working token that cannot write, with no error at any point in the flow. The failure shows up much later, as a 403 on your first write — usually in production, usually from a user report.
Omitting scope grants media:read only. Not both. This applies to the authorize endpoint and the device endpoint alike.A write-capable app must ask for media:write explicitly. There is no “default to everything” behaviour — the safe default, but not the one most people assume.
An unrecognised scope string is silently downgraded to read-only. Scope parsing looks for the exact lowercase token media:write. If it is not there, write is simply not granted and no error is raised.So media:wrote, media.write, a bare write, and MEDIA:WRITE (wrong case) all get you a read-only token that works perfectly until the first write.
The separator is more forgiving than the spelling. Because the check is for the presence of media:write rather than a parse of the list, a separator mistake like media:read,media:write still grants write — the token is in there either way.That asymmetry is worth knowing so you debug in the right place: if writes are failing, suspect the spelling or case of media:write, not your delimiter. Send the space-separated form regardless — it is what the spec calls for and what we will keep supporting.
The defence for both is the same, and it is one line: read the scope field in the token response and compare it to what you asked for.
Failing loudly at authorization time turns a confusing production 403 into an obvious configuration bug on your own machine.

How scope is enforced

Enforcement is by request shape rather than by a per-endpoint list:
  • A GET with no request body needs media:read.
  • Everything else needs media:write — every other method, every request that carries a body, and all of /scrobble and /checkin even on GET.
A request that exceeds its token’s scope is rejected with 403 and an insufficient_scope error, along with a WWW-Authenticate header naming the scope required.
A GET that carries a body counts as a write. Attaching a payload to a GET is the one way to get a surprising 403 on a read-only token — and some HTTP helpers do it for you if you hand them a JSON argument without thinking about the method. If a read is failing with insufficient_scope, check whether your client is sending a body it does not need.The rule is deliberately blunt. Scope is decided before anything looks at which endpoint you called, so a request that carries a payload is treated as a write on principle rather than judged case by case.

Changing scope later

You cannot widen a grant by refreshing. On the refresh grant:
  • a narrower scope is ignored and the original is reissued
  • a wider scope is rejected with invalid_scope
To change what your app can do, send the user through authorization again with the scope you want. That creates a new, independent grant — it does not modify or replace the existing one, so discard the old tokens yourself if you no longer want them.

See also

OAuth flow

Where the scope parameter goes.

Tokens and refresh

Why refreshing cannot change your scope.

Migrating from V1

Scopes are new in V2 — what that means when you port existing code.

Errors

The full error vocabulary, including insufficient_scope.