Skip to main content

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. * 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: 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).
  • 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: 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.