domainwidedelegation.comGet it set up

unauthorized_client — the delegation error everyone hits

The message is unhelpful on purpose: Google will not tell you which scope failed, because that would leak what is authorized. Here is what it means and how to find the culprit systematically.

RefreshError: ('unauthorized_client: Client is unauthorized to retrieve
access tokens using this method, or client not authorized for any of the
scopes requested.', ...)

What it actually means

The token exchange was rejected before any API was called. Google matched your service account's client ID against the domain-wide delegation grants for that Workspace domain and concluded that the pairing of this client with these scopes is not authorized. Note the "any of the scopes" phrasing — one unauthorized scope in a list of twenty fails the entire request.

Causes, in the order they actually occur

1. A scope in your code is not in the Admin console grant

This is the overwhelming majority. Someone added a capability in code — a new API, a read-write upgrade — and never went back to the Admin console. Compare the two lists character by character; they are matched as exact strings.

2. Right scopes, wrong tenant

If your service account is delegated in more than one Workspace domain, the grant lists are independent. Impersonating a user in tenant B with a scope only authorized in tenant A produces exactly this error. The subject you impersonate determines which tenant's grant applies.

3. The client ID in the console is not the one your key belongs to

Rotated keys, a second service account created during testing, or the service account email pasted where the numeric client ID belongs. Print the client_id field from the JSON key and compare it to the console entry.

4. The subject is not a real, active user

A typo, a suspended account, or a group address rather than a mailbox. Delegation impersonates users; it cannot impersonate a group.

5. The grant has not propagated yet

New grants are usually quick but not always instant. If everything looks right, wait a few minutes and mint a fresh token — a cached one will keep failing regardless.

The isolation method

Rather than staring at two lists, ask for one scope at a time. The scopes that fail are exactly the ones missing from the grant, and you get the answer in one pass:

from google.oauth2 import service_account
import google.auth.transport.requests

KEY     = "/secure/path/key.json"
SUBJECT = "person@yourdomain.com"
SCOPES  = [
    "https://www.googleapis.com/auth/gmail.readonly",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/calendar",
    "https://www.googleapis.com/auth/admin.directory.user.security",
]

req = google.auth.transport.requests.Request()
for scope in SCOPES:
    c = service_account.Credentials.from_service_account_file(
        KEY, scopes=[scope]).with_subject(SUBJECT)
    try:
        c.refresh(req)
        print("AUTHORIZED  ", scope)
    except Exception as e:
        print("NOT GRANTED ", scope, "->", repr(e)[:90])

Every line marked NOT GRANTED is a scope to add in the Admin console. Add them, wait a moment, and re-run until the list is clean.

A real example of cause 2. Admin SDK scopes are the classic trap. Teams authorize the everyday Gmail, Drive and Calendar scopes, then months later write code that lists or revokes user OAuth tokens — which needs admin.directory.user.security. Nothing about the error says "you are missing an Admin SDK scope"; it says unauthorized_client, exactly as if the whole delegation were broken.

What this error is never

Still failing after all five?

Multi-tenant grants and Admin SDK scopes are where this stops being a five-minute fix. We debug and build these setups for a living — $500 per hour, and this class of problem is usually resolved well inside one.

Get help with your delegation