Access control
Roles, permissions, and the rules Spec0 uses to decide whether a request from your dashboard, CLI, or CI is allowed. What we accept, what we reject, and why.
Every action you take in Spec0 — opening a page in the dashboard, publishing a spec from CI, calling a public API endpoint with a personal token — runs through the same access-control engine. This page is the contract: who can do what, under what conditions, and what to expect when a request is rejected.
The five-step decision
When you make a request, Spec0 runs through these steps in order. The first step that produces a verdict wins — later steps don't run.
| # | Step | When it allows | When it denies |
|---|---|---|---|
| 1 | Platform super-admin | Caller has platform:super_admin (Spec0 staff only). | Otherwise → fall through to step 2. |
| 2 | Same organisation | Resource you're acting on belongs to your org. | Cross-org access → deny, regardless of role. |
| 3 | Org admin override | You have organisation:admin or organisation:manage AND it's your org. | Falls through to step 4 if you're not an org admin. |
| 4 | Resource-specific rule | The rule for this resource type (API, team, subscription, mock server, etc.) accepts the action. | Resource rule rejects → deny. |
| 5 | Permission check | You hold the <resource>:<level> permission needed for the action (e.g. api:manage to edit an API). | Missing permission → deny. |
Cross-organisation requests are structurally impossible — they fail at step 2, and the database-layer query filter (see ADR-0010) means even a buggy backend couldn't accidentally return another org's data.
Roles
Every user in your org carries one or more roles. Roles are how you grant permissions in bulk — instead of assigning api:view, team:view, subscription:view etc. one by one, you assign Org Member and the corresponding permissions come with it.
| Role | Scope | What they can do |
|---|---|---|
| Org Owner | Whole org | Everything in the org. The original signup user; can transfer ownership. Holds every :admin permission. |
| Org Admin | Whole org | Manage members, teams, APIs, subscribers, mock servers. Cannot transfer ownership or delete the org. |
| Org Member | Whole org | Read-only across the org. View APIs, teams, subscribers, mock servers. Cannot create or edit. |
| Team Admin | One team | Manage their team's members, APIs, subscribers, and mock servers. Mint service-account tokens for the team. Cannot touch other teams. |
| Team Member | One team | Edit APIs and mock servers owned by their team. Read-only on subscriptions and team membership. |
| Subscription Admin | Subscription | Approve, modify, or revoke specific subscriptions. Used by teams that mostly consume APIs without owning them. |
| Platform User | Platform-wide | Read-only across published APIs and subscriptions. Used for cross-org integrations. |
Spec0 staff also carry Platform Admin (backoffice console access) or Platform Super-Admin (every permission). These never apply to customer accounts.
Permissions
A permission is <resource>:<level>. Levels are hierarchical: admin includes manage includes view.
| Permission | Granted by | What it lets you do |
|---|---|---|
organisation:view | Every org role | See your org's profile, members count, billing tier. |
organisation:manage | Org Admin, Org Owner | Edit org profile, invite/remove members, change plans. |
organisation:admin | Org Owner | Transfer ownership, delete the org. |
team:view | Org Member, Team Member | See team rosters and ownership. |
team:manage | Org Admin, Team Admin | Add/remove team members, mint service-account tokens, edit team metadata. |
team:admin | Org Owner, Team Admin | Delete a team, transfer team ownership. |
api:view | Org Member, Team Member | Read API metadata, fetch published specs, see version history. |
api:manage | Org Admin, Team Member, Team Admin | Publish new spec versions, edit API metadata, configure governance. |
api:admin | Org Owner | Delete an API, transfer API ownership. |
subscription:view | Org Member, Team Member | List subscribers, see subscription details. |
subscription:manage | Org Admin, Team Admin | Approve, reject, or modify subscriptions on your team's APIs. |
subscription:admin | Org Owner, Subscription Admin | Force-delete a subscription, override approval gates. |
mock_server:view | Org Member, Team Member | See mock servers and their variants. |
mock_server:manage | Most roles | Create, edit, delete mock servers and response variants. |
mock_server:admin | Org Admin, Org Owner, Team Admin | Reset mock-server state, rotate keys. |
The platform also defines user:* and system:* permissions — those are reserved for Spec0 staff.
How team boundaries work
A few resources are team-scoped — APIs, mock servers, subscriptions. Even if you have api:manage, you can only modify APIs that your team owns. This means:
- A Team Member of Team A who has
api:managecan publish new versions of Team A's APIs but cannot publish to Team B's APIs (denied at step 4: resource-specific rule). - A user with no team assignment cannot perform any non-VIEW action on team-scoped resources, even if they have permissions.
- An Org Admin or Org Owner overrides this at step 3 — they can act on any team's resources within the org, no team membership required. This is by design and matches how org admins work in tools like GitHub or Linear.
Org-scoped resources (the org profile itself, member invitations, billing) don't have team boundaries — only org isolation and permission checks apply.
Accepted vs. rejected — examples
These walk through the five-step decision against real situations.
A team admin mints a service-account token for their own team.
- Not super-admin → continue.
- Same org ✓ → continue.
- Has
team:managefrom Team Admin role, but notorganisation:admin→ continue. - Resource =
team, action =manage. Resource rule: same-team check.userTeamId == resourceTeamId✓ → continue. - Has
team:managepermission ✓ → allowed.
The same team admin tries to mint a SAT for a different team.
- Not super-admin → continue.
- Same org ✓ → continue.
- Not an org admin → continue.
- Resource rule: same-team check.
userTeamId ≠ resourceTeamId→ denied at step 4.
An org admin mints a SAT for any team in their org.
- Not super-admin → continue.
- Same org ✓ → continue.
- Has
organisation:manageAND same org → allowed (skips step 4 entirely).
An org member of Org A views an API published by Org B (e.g. via a leaked URL).
- Not super-admin → continue.
userOrgId ≠ resourceOrgId→ denied at step 2. The Hibernate org filter additionally guarantees the read returns nothing even before authz runs.
A user with no team assignment tries to edit one of their org's APIs.
- Not super-admin → continue.
- Same org ✓ → continue.
- Not an org admin → continue.
- Resource rule: API is team-scoped, action is
manage. User has nouserTeamId→ denied at step 4.
A CI service-account token (SAT) tries to publish a spec.
- Not super-admin → continue.
- Same org ✓ → continue.
- SAT carries the team's permissions, not org-admin → continue.
- SAT's
userTeamIdis the team it was minted for; spec's API belongs to the same team ✓ → continue. - Token's scopes include
write:specs✓ → allowed. (See Public API tokens for how scopes map to permissions.)
What you'll see when something is rejected
The HTTP response is always 403 Forbidden with this shape:
{
"timestamp": "2026-05-02T10:42:00Z",
"status": 403,
"error": "Access Denied",
"message": "Access denied",
"reason": "User is not in same team as the API resource"
}The reason field tells you which step rejected the request — useful for debugging. Common reasons:
reason | What to do |
|---|---|
User org ... ≠ Resource org ... | You're trying to act on another org's resource. Check the URL or IDs. |
User is not in same team as the ... resource | You're not on the team that owns the resource. Ask a team admin to add you, or have an org admin perform the action. |
User has no team assignment - cannot access team-scoped ... | You're an Org Member with no team. Ask an Org Admin to add you to a team. |
User lacks required permission: ... | Your role doesn't include the permission. Check the permissions table above. |
... team ID is null - cannot perform ... operation on Team without team context | The endpoint expected a team-scoped path but the URL didn't include one. Check the API reference. |
Where to go next
- Teams — the multi-tenant model the access control runs against.
- APIs — what
api:*permissions actually let you do. - Subscriptions —
subscription:*and the approval flow. - Tokens — PAT vs SAT vs org API key, and how token scopes interact with the permissions on this page.