Skip to content

Troubleshooting and FAQ ​

When someone is refused, the fastest way to find out why is the decision id. This page shows what a refusal looks like in each API dialect, how to replay it in the IAM center, what 403 and 503 mean, and answers the questions that come up most often.

Requires a Console release with the IAM engine

Decision ids, the Access check and the authorization_unavailable behaviour are part of the IAM engine.

Decision ids ​

Every refusal carries a decision id, so that the person who was refused and the person who investigates talk about the same event:

  • a 403 from the permission gate has decisionId in the body and an X-Decision-Id header; OpenAI- and Anthropic-compatible routes put it in error.decision_id;
  • a refusal to act for a user (401, 403 or 503) carries one too, in the same places;
  • a refusal at a portal shows the person a reference code, which is a decision id;
  • paste it into the IAM center (Settings → IAM center → Access check → Look up a denied request), or call GET /api/iam/decisions/{decisionId}: you get the audit row and a ready-to-run access check that reproduces the decision, the policies that decided it by name (with whether each is still in force) and, for a portal refusal, the action, the portal or agent and the channel;
  • a decision resolves while its audit row is in the 30-day window the Denials feed looks at.

What a refusal looks like ​

json
{
  "error": "Forbidden: mcp requires write permission",
  "message": "Forbidden: mcp requires write permission",
  "code": "forbidden",
  "service": "mcp",
  "required": "write",
  "decisionId": "7c0f6b2e-5d1a-4c1e-9a53-0d9b8a1f2e44"
}
json
{
  "error": {
    "message": "Forbidden: mcp requires write permission",
    "type": "permission_error",
    "code": "forbidden",
    "decision_id": "7c0f6b2e-5d1a-4c1e-9a53-0d9b8a1f2e44"
  }
}
json
{
  "type": "error",
  "error": {
    "type": "permission_error",
    "message": "Forbidden: mcp requires write permission",
    "decision_id": "7c0f6b2e-5d1a-4c1e-9a53-0d9b8a1f2e44"
  }
}

error is the human-readable reason, so a client that only shows body.error shows the reason.

Refusal codes ​

StatusCodeMeaning
403forbiddenThe caller's effective level on the service is below what the call needs. Carries service, required and decisionId.
403route_not_declaredThe route declares no authorization. A healthy build has none; GET /api/iam/engine/stats lists them.
403session_forbiddenA session was started by someone else and the caller may not view or change it. See agent sessions.
403action_forbiddenThe caller may not answer a pending tool approval or question. See approvals.
403files_write_requiredNaming the bucket of an agent's session files needs write access to that bucket.
403permit_requires_adminAn enabled allow policy can only be saved by an owner or admin.
403ip_not_allowedThe token has an IP allowlist and the call came from another address. Written to the audit log.
403delegation_refused, project_not_accessibleSee acting for a user.
401delegation_not_enabled, invalid_assertion, expired, untrusted_key, unknown_principal, disabled, seat_limit, conflictSee acting for a user.
503authorization_unavailableThe engine could not decide. Not a permission error: retry.
503delegation_unavailableThe stores behind an acting-for-a-user call could not answer. Retry.

When authorization is unavailable ​

If the engine cannot decide a request (the person's data cannot be read, the engine fails), the request is refused, never allowed and never decided by some other rule set:

http
HTTP/1.1 503 Service Unavailable
Retry-After: 5
X-Decision-Id: 0b6f2c3e-...

{ "error": "Authorization is temporarily unavailable", "code": "authorization_unavailable", "decisionId": "0b6f2c3e-..." }

OpenAI-compatible routes answer with type: "server_error", code: "authorization_unavailable" and decision_id; Anthropic-compatible routes with type: "api_error" and decision_id. A 503 is not a permission error: clients that retry on 503 (the Console SDK does, and honours Retry-After) simply try again. It is counted, logged and written to the audit log (outcome denied, metadata.code: "authorization_unavailable", also for GET), so it shows up in the Denials feed and resolves through its decision id.

Three safeguards keep that rare:

  • The server will not start without a working engine. At boot the engine loads Cedar, compiles its schema and the built-in policies and evaluates a few canned decisions. If any step fails the process exits with an explanation instead of serving requests it cannot decide, so a broken installation shows up when you deploy it, not as failing requests.
  • A broken custom statement does not take the workspace down. A statement that no longer loads is skipped and reported (the engine is degraded); the rest of the policy set applies.
  • Readiness reports it. GET /api/health/ready has an authorization-engine check: ok, degraded (requests were refused recently, or statements were skipped) or down (the canned decisions fail; the endpoint answers 503). If your orchestrator probes only the TCP port, point the readiness probe at this endpoint. Counters and the policy set of the workspace are in GET /api/iam/engine/stats.

The Access check ​

The Access check tab (Settings → IAM center → Access check) answers "can this person or token do this, and which rule decides?". Choose a person or token, an action (a service level) and, if you want, a project, channel, IP address and time. The result says Allowed or Denied and names the deciding rule: a built-in one (a role, a project limit, a token scope) or one of your policies. It does not change anything, so people who may only view the IAM center can use it, and nothing is recorded: a simulation is not traffic.

Extra inputs appear when the action needs them:

ForThe form also asks
A portal action in the portal channelThrough portal; a group can be the principal ("View as")
agent.approve, agent.session_view_others, agent.session_manage_othersSession started by; for approvals also The agent accepts as approvers
A rule with an IP conditionIP address
A rule with a validity windowAt

The Access check API ​

POST /api/iam/simulate (needs read on iam; at most 120 calls a minute per person):

bash
curl -X POST https://console.example.com/api/iam/simulate \
  -H "Content-Type: application/json" --cookie "token=..." \
  -d '{
    "principal": { "type": "user", "id": "<user id>" },
    "action": "models.write",
    "resource": { "kind": "service", "service": "models" },
    "context": { "projectId": "<project id>", "channel": "dashboard" }
  }'
  • principal: { "type": "user" | "token" | "group", "id" }. A group principal is accepted only for the portal actions in the portal channel.
  • resource: { "kind": "service", "service" }, { "kind": "resource", "type", "id" } or { "kind": "project", "projectId" }.
  • context: projectId, channel (dashboard, api, portal), ip, at (ISO timestamp), and for the portal and session actions portalId, embedded, test, ownerId and approvers (owner, owner_or_admin, project_members).

The answer:

json
{
  "allowed": false,
  "reasons": [
    { "cedarId": "custom:freeze-model-hub", "policyId": "freeze-model-hub", "managed": false, "name": "Freeze Model Hub for contractors", "effect": "forbid" }
  ],
  "defaultDeny": false,
  "principalSummary": { "type": "user", "id": "…", "label": "Ann Lee", "role": "user", "groups": [] },
  "projectId": "…",
  "enforcedAtGate": true
}

defaultDeny is true when nothing allowed the request (as opposed to a rule forbidding it). enforcedAtGate is false for a check about an individual resource, where the answer is what the policies say rather than what a real request would meet. session and portal describe what a session or portal check assumed.

The audit trail ​

IAM center → Audit lists denials and policy changes, with CSV export (GET /api/iam/audit and GET /api/iam/audit/export, kind=denials or kind=changes). A person's drawer under Principals shows their recent Activity. The Overview tab shows the latest denials and anything that needs attention.

FAQ ​

Do I have to write policies? No. Roles, group grants, project permissions and token scopes already decide everything. Policies are for exceptions.

Can I turn the engine off? No. There is one decision path. That is also why the engine is checked at boot.

A member lost access after the upgrade. Open their decision id in the Access check, or look at the person under Principals: the effective level of each service is there, with where it comes from (the role, a direct grant, a group, a token scope, a project limit or a policy). Most often it is a project permission (see Project permissions).

A person can open a service in one project but not another. Project service permissions cap the workspace level inside a project. Open the project's Members → Project service permissions and look at the service: No access caps it at none there.

An API token gets 403 but its owner can do the call. The token's scope is a cap: a service that is not in a scoped token's scope resolves to none, and {} allows nothing. Also check the token's project limits.

A token can do more than I expected. A token takes its owner's group grants too. Replace it with a scoped token, or take the owner out of the group.

Why does a deny rule not stop writes to an administration service? Changes there are checked as Write and need Admin on top, so deny Write to block changes.

IP rules never match behind my load balancer. Set TRUSTED_PROXIES (see Client address and TRUSTED_PROXIES).

The server does not start: "Authorization engine self-test failed". The message says which step failed (loading Cedar, the schema, the built-in policies or a canned decision). Check that the installation has its production dependencies (@cedar-policy/cedar-wasm), that the image is the one built for this release, and the log lines before the message.

I locked everybody out with a policy. Sign in as the workspace owner and disable or delete the rule. See Owner safety.

Is a background job restricted by a policy? No. Scheduled work runs without a principal; a policy restricts the person who starts something. See What policies do not govern.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub