Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Platform token returns 403 with "requires admin:read or admin:write scope"

Symptom#

Your service obtains a token via client_credentials at /api/v1/platform/oauth/token and the call succeeds. But when the service uses the token to call an admin endpoint (/api/v1/admin/...), it gets:

http
1
2
HTTP/1.1 403 Forbidden
{"detail":"Platform token requires admin:read or admin:write scope"}

Cause#

The admin API gates every endpoint on the JWT carrying admin:read or admin:write in its scope claim. A platform token's scopes come from one of two places:

  1. The scope form parameter you sent on the client_credentials request, or
  2. If you sent none, the application's allowed_scopes column (the registered scope superset).

If neither path put admin:read (or admin:write) into the token, the admin API rejects.

Fix#

Two things have to be true:

1. The application's allowed_scopes must include the admin scope#

Check your application's row in the admin UI (Applications → your app → Settings → Allowed Scopes). If admin:read isn't ticked, the application can never produce a token with that scope, regardless of what you ask for.

If the admin UI's scope picker doesn't show admin:read as a togglable option, the picker is limited to its hardcoded list — bump the application via API:

bash
1
2
3
4
curl -X PATCH "$SCAIKEY/api/v1/admin/applications/<app_id>" \
  -H "Authorization: Bearer <super_admin_token>" \
  -H "Content-Type: application/json" \
  -d '{"allowed_scopes":["openid","admin:read","admin:write"]}'

(Include any other scopes the app already needs — allowed_scopes is a replacement, not a merge.)

2. The token request must carry that scope#

Either include it explicitly:

bash
1
2
3
4
curl -X POST "$SCAIKEY/api/v1/platform/oauth/token" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "grant_type=client_credentials" \
  -d "scope=admin:read"

Or omit the scope parameter entirely so the token inherits the full allowed_scopes list.

Verify#

Decode the access token and confirm admin:read is in scope:

bash
1
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool

The scope claim should be a space-separated string containing admin:read (and/or admin:write).

Gotcha: stale cached tokens#

Bumping the application's allowed_scopes does not retroactively widen existing tokens. Issued JWTs are immutable — they carry whatever scopes they had at mint time. After changing the app's scopes, force your service to request a fresh token (clear any in-memory cache).

Updated 2026-05-17 12:20:38 View source (.md) rev 1