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

# Compliance endpoint permissions: the role matrix

> One table of which workspace role each high-traffic compliance read and write needs — BAA, SCIM, DSAR, audit export, PHI audiences, vertical bundles, fraud review, and agent identity — plus how to read a 403, scope-vs-role on agents tokens, and where your failed call is recorded.

# Compliance endpoint permissions: the role matrix

Role requirements for compliance endpoints live on each feature's own page,
so until now you reconstructed the matrix by opening seven pages. This page
centralizes it. Every row below names the gate exactly as the feature page
states it, so the table and the pages cannot drift — and the pinned test
that covers this page fails if they do.

Two readings of a 403 disappear once you see the table: it is either a
**scope** refusal (the credential's API scope did not cover the route) or a
**role** refusal (the member's workspace role fell below the route's
declared floor). The fix is different for each, and Section 3 shows which
one you got.

All endpoints below are rooted at
`https://api.orbit.devotel.io/api/v1`. "Reads" and "writes" split at the
usual HTTP-method line: `GET` is a read; `POST`/`PUT`/`PATCH`/`DELETE` are
writes.

## Section 1 — The per-endpoint role matrix

`R` = can read (GET). `W` = can write (POST/PUT/PATCH/DELETE). `—` = the
gate refuses with `403 INSUFFICIENT_PERMISSIONS`. Where two rows appear,
the endpoint family splits read and write gates.

| Endpoint family | `owner` | `admin` | `developer` | `viewer` | `billing` |
| - | - | - | - | - | - |
| BAA — read state / preview / download (`/compliance/baa/*`) | R/W | R/W | — | — | — |
| BAA — execute / revert (`/compliance/baa/execute`, `/compliance/baa/revert`) | W | — | — | — | — |
| SCIM — token settings & mapping (`/settings/scim`) | W | — | — | — | — |
| DSAR — operator-filed requests (`/compliance/dsar`) | R/W | R/W | — | — | — |
| Audit export (`/compliance/audit-exports`) | R/W | R/W | — | — | — |
| PHI audiences (`/compliance/hipaa/phi-audiences`) | R/W | R/W | — | — | — |
| Vertical bundles — reads (`/compliance/vertical-bundles`, activation state) | R | R | R | R | — |
| Vertical bundles — activate / checklist toggles | W | W | W | — | — |
| Fraud review — reads (`/compliance/fraud-reviews`) | R | R | R | R | R |
| Fraud review — acknowledge / dismiss / thresholds | W | W | — | — | — |
| Agent identity — inventory (`/settings/agent-identities`) | R/W\* | R/W\* | — | — | — |
| Agent identity — bulk suspend | W\* | W\* | — | — | — |

\* Agent-identity routes additionally require the `agents:read` scope (the
inventory) or `agents:write` (the bulk suspend) on the credential — see
Section 2.

Notes that the bare grid cannot hold:

* **DSAR also has a public path.** A data subject files without any role
  at all through the unauthenticated
  `/compliance/public/dsar/*` endpoints (Turnstile + OTP). The matrix
  covers operator calls; the public flow is how your customers' rights
  arrive.
* **BAA splits owner-only from owner/admin.** Reading the state,
  previewing the template, downloading the executed copy, and the
  require/decline attestations all admit `owner` **or** `admin` — but
  `execute` and `revert` are `owner`-only because they bind or unwind a
  legal agreement. An admin starring as "the compliance person" on an
  execute call is the single most common mistaken hand-off.
* **Vertical bundles and fraud review read lower than they write.**
  Bundles admit any of the four standard workspace roles on reads
  (`owner`/`admin`/`developer`/`viewer`) but drop `viewer` on writes.
  Fraud-review reads go to any authenticated member — including `billing`
  — so an on-call accountant can investigate an alert, but only
  owner/admin can acknowledge, dismiss, or move thresholds.
* **SCIM settings are owner-only throughout.** Token issuance, role
  mapping, and group mapping are all owner-gated; there is no admin
  delegation on that surface.

## Section 2 — `agents:read` / `agents:write` scopes versus workspace roles

A 403 on an agent-adjacent route is one of two different refusals, and the
fix differs.

**Scope** is a property of the **credential**. An API key minted without
`agents:read` cannot read an agent surface no matter whose role it was
minted from; without `agents:write` it cannot write. Scope travels with
the key, so passing an admin's viewer-scoped key still fails the scope
check.

**Role** is a property of the **member**. When you call through the
dashboard or a session token, the member's workspace role is what the gate
reads. When you call with an API key, the role of the member the key was
minted under is what the gate reads.

Agent-identity governance needs **both** — the inventory gates on the
`agents:read` scope AND an owner/admin role; the bulk-suspend gates on the
`agents:write` scope AND an owner/admin role. Neither alone is enough:

| What you hold | Inventory GET | Bulk suspend POST |
| - | - | - |
| Owner session (dashboard) | yes | yes |
| Admin's API key with `agents:read` but no `agents:write` | yes | 403 |
| Developer's API key with both scopes | 403 (role) | 403 (role) |
| Viewer's API key with both scopes | 403 (role) | 403 (role) |
| Owner's API key with neither scope | 403 (scope) | 403 (scope) |

The same dual gate shows up on the broader agent stack (guardrail
effectiveness, computer-use sessions, fine-tuning, authorization
mandates): reads on `agents:read`, writes on `agents:write`, each with a
declared role floor on top. If your call is to an agent route and you got
a 403 you cannot explain, check scope first — it is the less visible half.

## Section 3 — Reading a 403 and handing the call off

The error envelope tells you which layer refused. Read `error.code`
first:

* **`401 UNAUTHORIZED` / `401 INVALID_API_KEY` / `401 EXPIRED_TOKEN`** —
  no usable credential reached the gate at all. This is an authentication
  problem, not a permission problem: fix the key, its expiry, or its
  source IP (see [Authentication and API keys](/troubleshooting/auth-and-api-keys)).
* **`403 INSUFFICIENT_PERMISSIONS`** — the credential authenticated, but
  its role or scope does not cover the route. This is the matrix above.

Once you have a `403 INSUFFICIENT_PERMISSIONS`, the hand-off depends on
which row the endpoint sits on:

| What you tried | Who to ask | What to ask for |
| - | - | - |
| BAA `execute` or `revert` | An **owner** | Do it themselves — no delegation exists |
| SCIM token or mapping | An **owner** | Owner-only surface; the owner runs it |
| DSAR operator filing | An `owner` **or** an `admin` | Run the call, or elevate you to `admin` |
| Audit export | An `owner` **or** an `admin` | Run it for you, or elevate you |
| PHI audiences GET/PUT | An `owner` **or** an `admin` | Same |
| Fraud-review write (you are a viewer/billing reader) | An `owner` **or** an `admin` | Acknowledge/dismiss for you |
| Vertical-bundle activate | An `owner`, `admin`, **or** `developer` | The widest write floor of the families here |
| Agent-identity inventory or suspend | An `owner` or `admin` **with the right scope** | Elevate the role AND mint the key with `agents:read` / `agents:write` |

Do not escalate scope and role asks in the wrong direction. Elevating a
`viewer` to `admin` fixes every role refusal above, but it cannot fix a
missing `agents:write` scope on the key, and an owner-scoped key minted
under a `developer` still mis-carries role. Name which of the two you
need when you hand the call off — "elevate me" for a role refusal,
"re-mint the key with the scope" for a scope refusal, "both" when the
route gates both (see Section 2).

## Section 4 — Where your failed call is recorded

Every refusal above is recorded **against your organization**. Open
**Settings → Audit log** — the page is restricted to `owner` and `admin`,
which is why the failed caller cannot always self-serve their own
attempt. Filter by the actor (your own name), the target endpoint's
resource, or the time the call failed; each row carries the event code, the
resource, and the full detail JSON once you expand it. Write attempts that
succeeded land in the same ledger — the SCIM token-issuance, BAA
execute/revert, vertical-bundle activations and checklist toggles,
fraud-review acknowledges and dismisses, agent-identity suspends, and
DSAR filings all append an entry naming the actor — so the same page
answers both "why did my 403 happen" and "who executed this BAA."

For longer-horizon evidence — SOC 2 evidence hand-offs, GDPR Article-30
records — the audit-export family (owner/admin, per the matrix) bundles
the whole ledger into a hash-chained export an auditor can verify
independently: [Audit Export](/compliance/audit-export).

## Related references

* [Roles, teams, and permissions](/concepts/roles-teams-permissions) — the
  ten built-in roles, the hierarchy floor, and the custom-role grant model
  this matrix gates on.
* [BAA](/compliance/baa) — the owner-only execute/revert split in detail.
* [SCIM Provisioning](/compliance/scim-provisioning) — the owner-only
  settings and token surface.
* [Data Subject Access Requests](/compliance/dsar) — the operator-filed
  versus public-subject split.
* [Audit Export](/compliance/audit-export) — the owner/admin export family
  and the hash-chain verification.
* [PHI Audiences](/compliance/phi-audiences) — the owner/admin gate plus
  the BAA prerequisite.
* [Plugin Marketplace and Vertical Bundles](/compliance/plugin-marketplace) —
  where the any-member-read / three-role-write split comes from.
* [Fraud Review Triage](/compliance/fraud-review-triage) — the
  any-member-read / owner-admin-write split, end to end.
* [Agent Identity Governance](/compliance/agent-identity-governance) — the
  scope-plus-role dual gate.
* [Authentication and API keys](/troubleshooting/auth-and-api-keys) — the
  401 family this page splits from the 403 family.
* [Audit Log](/guides/audit-log) — reading the record of your own failed
  call.
