Errors
Every refusal is application/problem+json with a stable code and a request_id to quote to
support.
| Code | Status | What to do |
|---|---|---|
UNAUTHENTICATED | 401 | Send a valid access token. |
ACCESS_DENIED | 403 | The token is for another environment or audience, has expired, or the app is not active. Get a new token for this environment. |
CONSENT_REVOKED | 403 | The player withdrew access. Ask them to authorise again. |
USER_TOKEN_REQUIRED | 403 | This operation needs a token a player authorised, not an app token. |
INSUFFICIENT_SCOPE | 403 | Your app was not granted this capability. |
FEATURE_DISABLED | 503 | The capability is switched off here (always so in production today). |
These codes are the policy engine's own decision values (packages/developer-gateway/src/ policy.ts): a route is public, OAuth-only, token-gated or user-only, and every gate -- wrong
audience, expired token, revoked consent, wrong environment, revoked app, suspended developer,
missing scope -- resolves to exactly one of the codes above, tested against the same route
table the gateway uses. FEATURE_DISABLED is the fail-closed answer every disabled capability
gives, including the whole API in production today.
Reading a refusal
{
"type": "about:blank",
"title": "Feature disabled",
"status": 503,
"code": "FEATURE_DISABLED",
"detail": "This capability is switched off.",
"request_id": "req_..."
}
Quote request_id to support. Never retry a non-idempotent mutating request without the
Idempotency-Key header you sent the first time (Global Directive #4); retrying with a new key
risks a duplicate effect once production is enabled.