08.03 Permissions and the API
API permissions are SimpleRisk user permissions. The API key inherits the user's role, direct grants, admin flag, and team membership. To restrict an integration's capabilities, restrict the user's permissions; to broaden them, broaden the user's. There's no separate API permission model.
Why this matters
Most production integration mistakes trace to a permission misunderstanding. The integration submits a risk and gets a 403; the developer thinks the API is broken; the actual cause is the integration user lacks submit_risks. The integration reads risks and gets back an empty list; the developer thinks something's wrong; the actual cause is team-membership filtering. Understanding how permissions interact with API requests prevents most of these wrong-track debugging sessions.
The model itself is simple: API permissions are user permissions. The key inherits everything the user has — role, direct grants, admin flag, team memberships. There's no separate "API can do X but not Y" layer. Restricting the integration means restricting the user account behind the key; broadening it means broadening the user.
This article covers how this plays out in practice. For the broader permission model, see The Permission Model.
How API requests resolve permissions
When a request hits an authenticated endpoint:
- The API key is extracted and the user identified (see Authentication and API Keys).
- The user's effective permissions are loaded — the union of role-derived permissions, direct per-user grants, and admin override.
- The user's team memberships are loaded.
- The endpoint handler executes the operation as that user. Permission checks (
check_permission(...)) and team filters apply normally.
The same code path runs whether the user came in via the web UI or the API. There's no separate "API permission" branch.
What this means in practice
The integration user's role is the integration's role
If you create a user slack-bot with the role "Risk Submitter," the API integration acting as that user can do exactly what a Risk Submitter can do — submit risks, view risks, but not modify configuration or other users. Want the integration to be able to close risks too? Either add the close-risks permission to the Risk Submitter role (affecting every Risk Submitter, not just the integration) or grant the permission directly to the slack-bot user.
The pattern: create one SimpleRisk user per integration, assign the role that matches the integration's needs, add per-user permission grants for any integration-specific capabilities the role doesn't cover.
Team membership filters the integration's view
A user on the Engineering team only sees Engineering's records. The API enforces this — GET /api/v2/risks returns risks visible to the user, not all risks in the database.
If your integration needs to see risks across multiple teams (e.g., a centralized dashboard), the integration user needs team memberships covering those teams. For "see everything" integrations, either:
- Add the user to every team, OR
- Set the user's
adminflag to1(which bypasses team filtering entirely, but is a much broader grant).
The admin-flag approach is convenient but dangerous — the integration can do anything in SimpleRisk, including modify other users' records and change critical configuration. Prefer adding the user to specific teams when possible.
Permission-denied responses
When the user lacks permission for an operation:
- 403 Forbidden — the standard response for permission denial.
- The response body includes a
status_messageindicating the denial. - The audit log records the attempt (object-level access denials are logged at
warninglevel per the SimpleRisk log conventions).
The 403 isn't an authentication failure — the key is valid; the user just can't do the requested operation. Check the user's permissions in SimpleRisk's User Management UI.
Admin override
A user with admin = 1 bypasses all permission checks. The API key for an admin user can do anything: submit risks, modify users, deactivate Extras, change settings, generate other users' API keys.
This is appropriate for human-operator accounts; it's almost never appropriate for integration accounts. Compromise of an admin key compromises the entire SimpleRisk install. Use admin keys exclusively for break-glass scenarios; create dedicated, least-privileged users for routine integrations.
Per-user direct grants
The User Management UI lets you grant specific permissions to a user beyond their role's default. This is useful for integration users that need a slightly-different permission set than any defined role:
- Pick a role that's mostly right.
- Grant the additional permissions directly to the user.
- The user's effective permission set is the union.
For integrations that recur, the grant pattern often repeats — at which point it's worth defining a new role specifically for that integration pattern.
Designing permissions for integrations
Pattern A: Read-only reporting integration
Goal: a script that reads risk data for an external dashboard.
- Role: a custom "API Reader" role with view permissions for the relevant entities (
view_risks,view_compliance, etc.) and no write permissions. - Teams: every team the dashboard should display data for.
- Admin flag:
0. - Verify: the user can
GET /api/v2/risks(returns expected data) butPOST /api/v2/risks/submitreturns 403.
Pattern B: Risk submitter integration
Goal: a script that imports risks from an external source weekly.
- Role: a custom "API Submitter" role with
submit_risksand the necessary related permissions. - Teams: the team(s) the imported risks should be assigned to (so they're visible to the right users via team-segregation).
- Admin flag:
0. - Verify: the user can
POST /api/v2/risks/submit(creates a risk) and the created risk appears for users on the same team(s).
Pattern C: Risk-state-update integration
Goal: a CI pipeline that closes risks when corresponding fixes ship.
- Role: a custom "API CI" role with
modify_risksandclose_risks(and the related team-scoped equivalents). - Teams: the team the risks the CI manages belong to.
- Admin flag:
0. - Verify: the user can
PATCH /api/v2/risks/{id}to update the relevant risks but can't modify other team's risks (403).
Pattern D: Admin operations integration
Goal: a script that bulk-creates users from an HR feed.
- Role: includes
manage_usersand related admin permissions. - Teams: not relevant for user management.
- Admin flag:
0ifmanage_usersalone is sufficient;1only if you genuinely need admin override. - Verify: the user can
POST /api/v2/usersto create users, and the created users get the right initial role/team.
Common pitfalls
A handful of patterns recur with API permissions.
-
Generating keys against admin users. The compromise blast radius is enormous. Use dedicated, least-privileged integration users.
-
Forgetting that team membership filters the integration's view. "Why does my integration see only some of the risks?" — usually the integration user isn't on the team that owns the missing risks.
-
Granting
admin = 1to fix permission issues. Diagnose the actual missing permission and grant that specifically; don't paper over with admin override. -
Not creating a per-integration user. Sharing one user across multiple integrations means rotating the key affects all integrations and audit attribution is ambiguous.
-
Granting permissions ad-hoc to individual users instead of defining roles. Three integrations with the same permission profile should use the same role; otherwise role-management drift over time produces inconsistent integration behavior.
-
Not testing the integration's permissions before going live. "It works for the developer's admin account; deploy to production" → first 403 in production is the integration first proving it lacks the actual permission.
-
Treating 403 as a transient error. It's not; the user genuinely lacks the permission. Check permissions; don't add retry logic.
-
Forgetting that some endpoints require the user be on a specific team. A risk endpoint may filter by team; a configuration endpoint may require admin. The endpoint's permission check is endpoint-specific.
-
Confusing read permissions with team filtering. A user with
view_riskspermission and membership in the relevant team can see those risks; either alone isn't enough for cross-team visibility. -
Granting permissions for hypothetical future operations. "Just give it
manage_usersin case." No — give it what it needs now; expand when needed. Over-granting is a liability.