Insufficient permissions
The 403 that will not tell you which permission was missing — how to work it out, plus two refusals that are wiring gaps you cannot fix.
Somebody signed in successfully and then hit a wall. This article covers the five refusals that look alike and mean different things, how to find out which permission a call actually needed, and two cases where the honest answer is that the refusal is a gap in our wiring and there is nothing on your side to change.
The refusals, and what each one means
| Body | Status | Meaning |
|---|---|---|
{"error":"missing JWT claims"} | 401 | No valid session at all. The token is absent, malformed or expired. This is an authentication problem, not a permission one — start at Signed out unexpectedly. |
{"error":"insufficient permissions"} | 403 | You have a session, we know which organisation, and your role does not hold the permission this route requires. This is the ordinary case and the fix is a role change. |
{"error":"permission check failed"} | 500 | We could not evaluate the check. Ours, not yours. Retry, then tell us. |
{"error":"token carries no organization"} | 403 | Your session names no organisation. Usually because the person belongs to more than one — a known limitation, described below. |
{"error":"organization mismatch"} | 403 | The organisation in the address is not the one your credential belongs to. A pasted link for the wrong tenant, or the multi-organisation case again. |
step-up authentication required | 403 | A second, layered check wants a fresh re-authentication. The console does not send the signal it looks for, so this is a wiring gap. See its own section. |
A small operational note if you are automating: insufficient permissions and its siblings are written with Go's plain error helper, so the body is JSON but the Content-Type header says text/plain. A strict client that trusts the header will not parse it. Match on the status code and the substring rather than on a parsed object.
If the caller is an API token rather than a person, the vocabulary is different again and the distinctions are useful: 401 {"error":"invalid api key"}, {"error":"api key revoked"} and {"error":"api key expired"} tell you which of the three happened, and 403 {"error":"insufficient api key scope"} is the token-level equivalent of a role denial. The plain authentication failures — missing authorization header, invalid authorization format, invalid token — are all 401 and mean the request never reached an authorisation check at all.
Finding out which permission the call needed
The 403 does not name the missing permission. That is a real shortcoming and we are not going to pretend otherwise — a message reading insufficient permissions: this route requires siem:alerts.write would resolve most of these tickets without a support call. Until it exists, here is how to work it out in about two minutes.
List every permission that exists
GET /api/v1/permissionsreturns the whole catalogue. Permissions are namedresourceplusaction— for exampleusersandread, oriam:auth_policiesandwrite. The catalogue is large; roughly a hundred entries, grown by migration over time.List what the person's role actually holds
GET /api/v1/roles/{roleID}/permissions. Compare that against the catalogue. In practice you are looking for the resource that matches the area of the console they were in —users,roles,organizations,sso,iam:network_zones,iam:auth_policies,iam:federation,integration:webhooks,pam:*,siem:*,xdr:*.Match the verb to the action
Reading a page is
read. Creating or editing iswrite. Removing isdelete. If they could see the page and not save, they holdreadand notwrite.Grant it, or move them to a role that has it
POST /api/v1/roles/{roleID}/permissionsadds one to a custom role. Prefer moving the person to an existing role over widening a role that other people share — widening a role is invisible to everybody else who holds it.
Two further consequences of the model are worth knowing. A membership carries exactly one role, so permissions are not additive across roles — you cannot give somebody a second role to top them up. And a person with no membership in an organisation gets a refusal for everything in it, which is the single most confusing failure mode in the product, because they can sign in perfectly and then be refused every page. If a brand-new user is refused everywhere, check that they have a membership before you check their role. Users created by our bulk import have none at all — see A bulk import was rejected.
`token carries no organization`
Some routes are not organisation-scoped in their URL and take the organisation from the session token instead — Sentinel-Q, billing and the GDPR subject-request queue among them. If the token carries no organisation, those routes fail closed with 403 and this message.
Failing closed is correct and it is a fix, not a bug: this used to be a silent pass, which meant anybody with a session could reach several of those routes regardless of their role. But it does surface a real limitation, because there is a legitimate way to end up with no organisation in your token: belonging to more than one. There is no organisation selector to resolve it with. That case, and what to do today, are covered in Signed out unexpectedly.
The step-up wall — a wiring gap, not a setting
There is a second, more elaborate authorisation path in the product. It layers device trust, network trust, region, incident state and a step-up requirement on top of the ordinary permission check. Its decisions are returned as plain sentences, and two of them are the ones people arrive here having seen:
insufficient permissions
step-up authentication required
action requires elevated delegated role
organization membership required
organization not found
request region does not match the serving region
tenant placement metadata is missing for this serving path
tenant is not assigned to this serving cell
request cell does not match tenant placement
request placement version does not match tenant placementIn that model, users.write, roles.write and organizations.write always require a step-up — a re-authentication performed within the last fifteen minutes — and api_keys.write, members.write and sso.write require one conditionally, when the device is low-trust, the network is untrusted, or an incident is active. action requires elevated delegated role fires when members.write, roles.write or sso.write is attempted by somebody who is not an administrator or a delegated administrator.
The ordinary REST surface — everything you reach from the console — is guarded by the simple permission check described in the first half of this article, not by this layer. If you are seeing insufficient permissions you are in the ordinary path and the fix is a role. If you are seeing step-up authentication required, you are in the layered path and you are stuck.
Delegated administration is not usable yet
action requires elevated delegated role names a role — delegated_admin — that the product intends to support: an administrator whose powers are scoped to a subset of the directory, so that a regional manager can reset passwords for their own region and nobody else's.
The scoping mechanism is not implemented. The reader that resolves a delegated grant is a stub that always answers no, there are no routes for creating a grant, and the function that would create one has no caller. So a delegated permission check can only ever fail. If you are trying to build a scoped-administrator model today, use custom roles with a narrow permission set instead, and accept that they are organisation-wide rather than scoped to a subset — see Custom roles and least privilege and Delegated administration.
Which of these you can actually fix
| Refusal | Yours or ours |
|---|---|
insufficient permissions | Yours. Grant the permission or change the role. |
token carries no organization | Mixed. The multi-organisation cause is a known limitation of ours; the workaround is yours. |
permission check failed | Ours. A fault, not a verdict. |
step-up authentication required | Ours. A wiring gap. Nothing you can configure. |
action requires elevated delegated role | Ours. The delegated-administration mechanism is not implemented. |
- The permission model — how resources and actions are named.
- Who can see and do what — the built-in roles.
- Custom roles and least privilege — building a narrower role properly.