Scope Subset Rule Violation
URI: https://docs.ankatech.co/errors/scope-subset-violation
HTTP Status: 403 Forbidden
When you see this
The request would have conferred on somebody else an authority the caller does not itself hold. A delegate never exceeds its delegator, so the conferral was refused whole — nothing was written, including the parts of the request the caller could have granted.
This is not an ordinary permission failure. The caller passed the endpoint's own scope check and is inside the tenant boundary; what it lacks is the authority it was trying to hand out. The status is deliberately the same 403 the generic refusal uses, and the distinct type is what tells the two apart — branch on type, never on the status.
Common Causes
- Assigning catalogue roles to a human user —
POST /api/v3/admin/tenants/{tenantId}/users/{userId}/rolesconfers the union of the submitted roles' scope sets. Any scope in that union the caller does not hold refuses the whole request. - Composing a tenant custom role from scope literals the caller does not hold.
- A delegated
admin.tenant.user.roles.update. That scope is itself composable into a custom role, so a tenant administrator can delegate exactly it. Without the subset rule, the holder could assignTENANT_ADMINISTRATORand acquire every scope the delegator had withheld. This refusal is that path closed.
Response Example
{
"type": "https://docs.ankatech.co/errors/scope-subset-violation",
"title": "Scope Subset Rule Violation",
"status": 403,
"detail": "Refused by the subset rule: a caller may not confer an authority it does not itself hold. The request would confer the following scope(s), which the caller does not hold: admin.tenant.key-backend.byok.read,admin.tenant.idp.mapping.manage",
"instance": "/api/v3/admin/tenants/7c9e6679-7425-40de-944b-e07fc1f90ae7/users/9c8b7a65-4321-4def-9876-0123456789ab/roles",
"timestamp": 1730000000,
"extensions": {
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"rule": "scope-subset",
"missingScopes": [
"admin.tenant.key-backend.byok.read",
"admin.tenant.idp.mapping.manage"
]
}
}
Content-Type: application/problem+json — the response follows RFC 9457 Problem Details.
Two extension members are specific to this refusal:
rule: the constant scope-subset, so a consumer can dispatch without parsing the sentence.
missingScopes: the offending scopes as an array, in the order the request conferred them, deduplicated.
missingScopes contains only scopes the request itself asked to confer and the caller does not hold. It is never drawn from the caller's own scope set and never from the catalogue, so the body discloses nothing the caller did not already submit — which is the whole reason naming them is safe.
How to Resolve
- Read
extensions.missingScopes. Those are exactly the scopes to act on; the rest of the request was admissible. - Either widen the caller's own roles to include them, or assign a narrower role that does not carry them. Retrying the same request unchanged always produces the same refusal.
- If the assignment looks ordinary — a built-in composite that a tenant administrator ought to be able to grant — the defect is in the manifest, not in the request: a built-in composite that is not conferral-closed makes an everyday assignment answer
403. Fix it inscope-catalog.json(see Scope Catalog*); never carve out or weaken the rule.
A refusal that names no scopes is a different error
When the caller's own scope set cannot be resolved at all, the rule fails closed and raises the generic .../errors/forbidden instead. No conferral was evaluated there, so there is no offending scope to name, and a body claiming an empty missingScopes would tell the caller they lack nothing while refusing them. missingScopes on this type is never empty.
For full schema definitions, examples, and interactive testing, see the Developer Hub Reference.