> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting access

> Working out why a request was denied, in the order that finds it fastest — and the cases where the error you get is not the one you would expect.

A denial tells you almost nothing on its own, deliberately: an error that
explained *which* statement refused would tell an attacker what your policies
look like. So diagnosis is a process of elimination, and there is an order that
finds the answer faster than reading policies top to bottom.

## Check scope before adding permissions

<Steps>
  <Step title="Check the credential's account">
    Service account and role tokens belong to an account. Confirm that it
    matches the requested account. Changing `X-Account-Id` does not move the
    identity; use an explicit AssumeRole exchange for another account.

    A non-owner human must choose an assigned account role. Their personal
    Workspace policies do not grant ordinary account operations.
  </Step>

  <Step title="Check the policy domain">
    Organization actions (`workspace:*`, `billing:*`, `quota:*`, and `audit:*`)
    need organization policies. Account service actions need account policies.
    A wildcard in the wrong domain is ineffective.

    A role session uses the role's own account policies and organization
    grants, not the source principal's permissions.
  </Step>

  <Step title="Check explicit deny and allow">
    A matching explicit deny wins in the evaluated domain. Without a matching
    allow, access is denied. For a user, organization policies include group
    policies. Service accounts never inherit group policies.
  </Step>

  <Step title="Check the resource CRN">
    Account IAM resources contain the account handle. Workspace resources
    contain the organization UUID in the path. Global means an empty region
    slot; it does not mean an empty account slot.

    Resource CRNs use immutable names for roles, service accounts, and policies.
    Trust principals bind concrete machine identities to immutable UUIDs.
    Copy the appropriate returned identity instead of guessing its shape.
  </Step>

  <Step title="Check conditions, boundaries, and session policy">
    A session policy can only narrow permissions. Account boundaries cap
    account permissions, while Workspace user boundaries cap organization
    permissions. Neither policy domain can be used to widen the other.

    Check condition keys, values, and whether a requested resource has the tags
    the policy expects. A missing value can make an otherwise matching allow
    ineffective.
  </Step>
</Steps>

## An assigned role cannot be assumed

Check both gates. The user needs a direct or group assignment for the target
role, and the role's trust must accept their Workspace user principal CRN.
For a machine source, replace the assignment check with `iam:AssumeRole` on the
target role in the source account policy.

For cross-account assumption, use the target account's full role CRN. Trust
must identify the source account and immutable source principal correctly.
Cross-organization role assumption is not supported.

## Organization delegation is denied

Attaching an organization policy requires Workspace permission on the policy
and IAM permission to update the target role or service account. Both must be
held by the same caller. An account administrator with no organization grants
cannot grant those to itself.

The same protection applies to sensitive changes to an identity that already
carries organization grants. See
[organization delegation](/workspace/permissions#delegating-organization-policies).

## Errors that can be misleading

<AccordionGroup>
  <Accordion title="404 where you expected 403" icon="search-x">
    A resource outside your organization or selected account can return `404`
    to avoid confirming its existence. Check scope as well as the UUID.
  </Accordion>

  <Accordion title="A revocation succeeds and still returns 403" icon="triangle-alert">
    The session revoke endpoint checks `iam:RevokeSession`, then reads the
    session for its response with `iam:GetSTSSession`. Grant both. With only
    the first permission, revocation can succeed before the read is denied.
  </Accordion>

  <Accordion title="An attachment grants access to only one side" icon="shield-alert">
    Managed policy attachment requires both policy and target resource
    permission. A grant on only policy CRNs or only identity CRNs is incomplete.
    See [IAM attachments](/iam/permissions#attaching-and-detaching-require-both-resources)
    and [Workspace permissions](/workspace/permissions).
  </Accordion>

  <Accordion title="A condition typo broadens a deny" icon="triangle-alert">
    Unknown operators do not match allow statements. For a deny with matching
    action and resource, an unknown operator fails closed. Check operator
    spelling when a deny is broader than expected.
  </Accordion>

  <Accordion title="Credential exchange is temporarily unavailable" icon="server">
    A `503 SERVICE_UNAVAILABLE` during a credential exchange can indicate a
    temporary platform dependency failure. Retry with backoff; rotating a
    valid key does not fix an unavailable exchange.
  </Accordion>
</AccordionGroup>

## Collection access and membership reads

A top-level list authorizes a collection CRN, not individual rows. A policy
on one role does not grant `ListRoles`. Relationship lists instead authorize
their parent. See [listing](/iam/permissions#listing-cannot-be-narrowed).

The region catalog is public. Token exchange verifies credentials. Humans can
read organizations they belong to through membership; machines need
`workspace:GetOrganization`. These checks differ from ordinary account
resource permissions.

## What the audit log does and does not show

The audit log records **successful** IAM and Workspace mutations — who attached which policy
to whom, who created a role, who removed a user. That is what you want for an
access review, and it is queryable through `GET /v1/audit-logs`.

<Warning>
  Authorization **denials are not in it**. A refused request is recorded in the
  service's own logs, with the action and the resource CRN the service actually
  asked about, but nothing surfaces that back to you through the API.

  So "what CRN did my policy have to match?" cannot be answered from the audit
  log. Derive it instead: the shapes are listed under
  [resources](/iam/permissions#policy-role-and-group-crns-use-immutable-names), and
  each service's permissions page gives its own.
</Warning>

Sign-in events are the exception that does record failures — a failed
two-factor enrolment or a removed security key is audited either way, because
the failure is the interesting half.

### Read event identities

Audit logs have no console page. Use the API to inspect the event identities
and retained labels.

The event's `crn` identifies the audit record itself, in the authenticated
organization. Its final segment is the event's `id`.
`actor_crn` and `resource_crn` identify the actor and target as they were at
event time. These snapshots do not change when a name changes or a resource
is deleted.

`actor_name`, `actor_email` and `resource_name` are optional retained labels.
Use them for display, not to construct an identity. They can remain available
after the actor or target has been deleted; handle absent or null labels.

For example, an API response can contain this audit record:

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "crn": "crn:audit:::log/550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-09-26T09:30:00Z",
  "actor_crn": "crn:workspace:::organization/990e8400-e29b-41d4-a716-446655440000/user/660e8400-e29b-41d4-a716-446655440000",
  "actor_name": "Alex Rivera",
  "actor_email": "alex@example.com",
  "action": "iam:CreatePolicy",
  "status": "success",
  "resource_crn": "crn:iam::production:policy/read-only",
  "resource_name": "read-only"
}
```

Historical events without identity snapshots return `null` CRNs. A target
that could not be resolved from event-time data also has a null `resource_crn`,
such as a password reset for an unknown email address. When a target UUID is
known, it is retained in `details.resource_id`; do not substitute that UUID or
a display label for a missing CRN.

An API record without snapshots or retained labels can look like this:

```json theme={null}
{
  "id": "770e8400-e29b-41d4-a716-446655440000",
  "crn": "crn:audit:::log/770e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-01-15T09:30:00Z",
  "actor_crn": null,
  "resource_crn": null,
  "action": "iam.policy.create",
  "status": "success",
  "details": {
    "resource_id": "880e8400-e29b-41d4-a716-446655440000"
  }
}
```

These examples show API JSON. CLI JSON output omits null CRN fields.

For an assumed-role actor, `actor_crn` identifies the role, such as
`crn:iam::production:role/deployer`. The optional `details.actor_session_crn`, shaped as
`crn:iam::production:sts-session/<id>`, correlates the event with its AssumeRole call.

## Next

<CardGroup cols={2}>
  <Card title="Permissions" icon="key" href="/iam/permissions">
    Every action, and the resource each is checked against.
  </Card>

  <Card title="Writing policies" icon="file-text" href="/iam/policies">
    Conditions, operators, and what a missing key does.
  </Card>
</CardGroup>
