Roles & Permissions
This page is the single source of truth for what each organization role can do. Organizations & Teams explains how roles are assigned; the MCP Reference documents each tool. This page answers one question: what can a Member do?
Two checks, not one
Every call against your infrastructure passes two independent checks, and both must pass:
- Tenancy — are you in the organization that owns this network, node, or service? Objects belonging to organizations you are not in are reported as not found, never as forbidden, so membership itself is not something an outsider can probe.
- Role — does your role in that organization permit this action?
They are separate on purpose, and the distinction explains the shape of the table below. Passing the tenancy check makes you a Member; it does not make you an Admin. It is also why Member is not a read-only role — a Member operates the fleet fully. What the higher roles add is authority over the organization itself, and over the handful of actions that change who else can reach something.
Capability matrix
| You can… | Owner | Admin | Member |
|---|---|---|---|
| View networks, nodes, services, and the member list | ✓ | ✓ | ✓ |
| Audit log — read the organization's tool-call history | ✓ | ✓ | — |
| Networks — create, delete | ✓ | ✓ | — |
| Nodes — register, provision, deprovision 1 | ✓ | ✓ | ✓ |
Node labels — node_label set / remove | ✓ | ✓ | — |
Node grants — node_grant, node_revoke | ✓ | ✓ | — |
Commands & files on a node — command, file, fs_*, the event bus | ✓ | ✓ | ✓ |
Elevated execution — admin: true on any of the above | ✓ | ✓ | — |
| Services — publish, update, delete, share | ✓ | ✓ | ✓ |
Service authentication — turning auth_required off | ✓ | ✓ | — |
| Org membership — invite, remove, change roles, rename the org | ✓ | ✓ | — |
| Billing — checkout, billing portal, spend cap | ✓ | — | — |
| SSO — connect an identity provider, enforce SSO login | ✓ | — | — |
| Org lifecycle — transfer ownership, delete the organization | ✓ | — | — |
An Admin cannot manage Owners or grant the Owner role. An organization always retains at least one Owner.
1 provision_node and deprovision_node also require provisioning to be enabled for your account — an account-level capability separate from your organization role. Without it the call is refused whatever role you hold.
Two of these are field-level, not tool-level
Two rows in that table gate a field inside a tool that any Member may call. This is the distinction most easily read wrong, so it is worth stating plainly.
admin: true — elevated execution
A Member may call command, file, the fs_* tools and service_publish. They may not send admin: true on any of them. The call runs as the account the node's owner configured; asking for the node's ambient identity — root on Unix, LocalSystem on Windows — needs Owner or Admin.
The gate is on the identity the operation will run as, not on whether the request spelled the field: updating a terminal service that is already stored admin: true needs the same authority even when you omit admin, because the stored identity carries over to whatever command you put in it. Lowering to the configured account never needs authority.
What "the account the node's owner configured" means, and what happens when there is none, is Execution identity below. The per-tool contract is in the MCP Reference.
auth_required: false — turning service authentication off
A Member may publish, update, delete and share services. They may not use service_update to set auth_required: false, or service_publish to create a service that way.
Publishing is an operational act, and everything a Member publishes is reachable only by someone who can authenticate. Clearing auth_required is the one edit that changes who can reach it — from someone with an account to anyone with the URL — which makes it a decision about the organization's exposure rather than about the service.
The gate is on the transition, and it is narrower than the admin one:
| Needs Owner/Admin | |
|---|---|
service_update moving auth_required from true to false | yes |
service_publish creating a service with auth_required: false | yes |
| Turning authentication back on | no |
Omitting auth_required — it means "leave as is", never false | no |
| Editing the title, target, or any other field of a service that is already unauthenticated | no |
That last row is deliberate. A service that was already public is public before your change and public after it, so an ordinary edit to it is an ordinary edit. Passing authorized_emails forces authentication back on, so it is never gated.
Execution identity
Roles decide who may ask. Execution identity decides what the work runs as once the asking is allowed. Two different questions, answered by two different people — your organization decides the first, the node's owner decides the second — and neither substitutes for the other. An Owner cannot be root on a node that refuses root, and a role no more grants an identity than an identity grants a role.
Four rules cover it. They hold everywhere the admin field appears: command, command_subscribe, file and the fs_* tools, service_publish and service_update, and the browser terminal behind a published service.
- You never run as admin unless you ask.
- The node decides what unelevated work runs as — not the caller, not us.
- A node can refuse admin entirely.
- A node that refuses admin must name an account, or it cannot run anything.
You never run as admin unless you ask
admin is a boolean and it defaults to false, so the ordinary case asks for nothing and gets nothing. What the first rule adds is the other half: a call that did not ask is never served the superuser either.
That is not automatic, because "unelevated" only means something if there is a lesser account to be. Where there is not — a root service install with no account configured, an account that was deleted or came off another machine, or user: root spelled out — the request is refused rather than quietly run as root, with failed_precondition (HTTP 403, not retryable). The refusal names both remedies, and the full treatment is Unelevated never means root.
The one case that is not refused is a node whose agent is already unprivileged — a rootless container, a user-level install — with no account configured. There admin: true would resolve to the very same identity, so there is no elevation to prevent, and the node serves unelevated work as itself.
The node decides what unelevated work runs as
The admin boolean picks between the two identities a node offers. It does not name an account, and no field does: the account is a property of the machine, set on the machine.
sudo nobgp config --user deploy
There is deliberately no remote setter — no tool, no app control, nothing over MCP. Changing the identity that gates a channel, through that channel, is using a tool to widen what the tool may do. It is the same reason allow-admin has no remote setter, and it is why every refusal above tells you to go to the box.
Only the node's owner knows which account unprivileged work should run as, so pick one that can actually do the work. Setting user: nobody to satisfy the requirement clears the refusal and buys nothing: a clear failed_precondition naming the problem becomes a scatter of permission failures that name a file.
A node can refuse admin entirely
allow-admin: false is the node owner's veto on superuser execution, set on the box like the account. It refuses rather than downgrades: an elevated request against it fails with permission_denied instead of silently running as somebody else, and passing the organization's role gate does not clear it. A node that refuses root refuses it to an Owner exactly as it does to a Member.
A node that refuses admin must name an account, or it cannot run anything
This one is the corollary rather than a separate decision, and it is the one worth checking before you narrow a node. allow-admin: false refuses everything elevated; the first rule refuses everything unelevated that would resolve to root. On a node with no configured account, those two between them leave nothing — no command, no file operation, no terminal session, no dispatched bus command.
# Refuses root, leaves the node fully operable as deploy
sudo nobgp config --allow-admin=false --user=deploy
# Refuses root on a node with no account: nothing can run at all
sudo nobgp config --allow-admin=false
The account must already exist on the machine; nobgp config refuses a name it cannot resolve, and refuses --user root for the same reason the first rule does. On a machine behind CGNAT the way back from the second form is console, RDP or SSH — see Recovering a node you have locked down.
Why node labels sit with node grants
Labels look like metadata, and mostly they are. But node_grant writes its targets as label pairs — "role=edge,site=irvine" — and targets are evaluated live, on every call. Relabelling a peer therefore moves it into or out of a granted node's reach, immediately, with no grant touched and nobody re-granting anything.
So a grant scoped by labels delegates part of its reach to whoever can apply those labels. That is why node_label carries the same Owner/Admin requirement as node_grant itself: the two tools decide the same thing from opposite ends.
(Separately, the role.* label namespace is reserved and refused by node_label outright, at every role — grants are made with node_grant, not written as labels.)
What a refusal looks like
A role refusal is always the same shape, so you can recognise it without guessing:
{
"code": "forbidden",
"message": "execute.elevated requires a higher role in this organization",
"retryable": false
}
Over HTTP it is a 403. The message names the action, so you can tell which gate you hit:
| Message | You tried to |
|---|---|
execute.elevated requires a higher role in this organization | send admin: true |
service.unauthenticated requires a higher role in this organization | turn a service's authentication off |
node.role requires a higher role in this organization | label a node, or grant/revoke a node role |
network.write requires a higher role in this organization | create or delete a network |
members.manage requires a higher role in this organization | invite, remove, or re-role a member, or rename the org |
billing.manage requires a higher role in this organization | open checkout or the billing portal, or set a spend cap |
sso.manage requires a higher role in this organization | configure or enforce SSO |
retryable: false is meant literally — nothing about the call will succeed on a second attempt. The remedy is either to drop the field that needed the higher role (admin, auth_required: false), or to have an Owner or Admin make the call.
A not found where you expected forbidden means the tenancy check failed, not the role check: the object belongs to an organization you are not a member of.
All three of the codes you can hit here are 403, and which one you got says whose decision it was:
| Code | Whose decision | What it means |
|---|---|---|
forbidden | the router, about you | your role in this organization does not permit the action |
permission_denied | the node, about everyone | one of its owner's vetoes — allow-admin, allow-tools, allow-roots |
failed_precondition | the node, about itself | it is not configured for what was asked — see Execution identity |
Only the first is answered by a role change. The other two are answered on the machine, and none of the three is retryable.
admin: true also has to pass the node's own allow-admin setting, which its owner controls on the box. Clearing the organization-role gate does not clear that one. See Owner vetoes and Node access control.