Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Who can do what in your company

How organizations, workspaces and teams differ, what each role can do, and why a second company cannot read the first.

ExplanationLast updated

On this page

Two departments share one company and one bill, and one of them handles employee records. The person who deploys the support chatbot should not be able to read what HR’s assistant was asked, and the HR lead should not be able to change the support chatbot’s model. Neither of them should have to ask you to click Deploy for them.

That needs three ideas kept apart, and most tools conflate at least two of them: a container for resources, a group of people, and a grant that connects the two. This page is those three ideas, the eleven roles built on them, and what a denial looks like when one of them stops you.

It assumes you have a company already; Create your company and first application is that path. It covers management access, not spending caps. Caps are a separate mechanism, described in The six limit types.

Three nouns that are not the same thing

Readers who skip this section reorganise their company twice.

An organization is your company. It owns the people, the workspaces and the billing relationship. A workspace is where related resources and their isolation settings live: environments, applications, LLM gateways and saved provider connections. A team is a group of people inside the organization, and nothing else.

A workspace and a team can carry the same name and still have no relationship. Changing a team’s roster moves no application data, and moving an application between workspaces changes no roster. Small companies keep one workspace and delegate at the application level instead; separate workspaces are for the case where two groups’ resources should not sit side by side, such as HR records beside a support queue.

Containment runs in one line: an organization contains workspaces, a workspace contains applications, an application contains gateways. One sentence about grants holds at every depth:

A grant made at a scope applies to that scope and to everything inside it, and to nothing above it or beside it.

So a grant on a workspace reaches all of its applications and their gateways. A grant on one application reaches that application’s gateways and stops there. It cannot be widened by the person who holds it.

The eleven roles, and what each one can do

A closed list of roles is the part a lead has to be able to read out loud to the person they’re granting it to, so plain capability is the primary description. The role IDs are the strings the API and the audit trail use.

Role What the person can do Where you can grant it
org_owner run the company: rename it, manage people, delegate access, read and change billing, and list every workspace, application and gateway organization
org_admin everything the owner can do except read or change billing organization
billing_admin read and change billing, and see that the company exists organization
workspace_admin run one workspace: manage its applications and gateways, edit configuration, deploy, manage keys and provider connections, run tests, and delegate inside the workspace workspace
app_developer edit one application’s gateway configuration, run tests, use an allowed provider connection, and see what its traffic cost application
env_deployer deploy to the environments the grant lists, and nothing else application, limited to named environments
analyst read a workspace’s or application’s metadata and its spend: cost, traffic, reliability, quality evidence workspace, application
content_reader read request and response content for the scope granted workspace, application, gateway
credential_manager create and revoke TokenTriage keys workspace, application
connection_manager add, test, rotate and disable saved provider connections, and allow their use workspace
runtime_service send traffic: invoke inference and write feedback in the environments the grant lists application or gateway, limited to named environments

Two consequences are worth saying out loud. An org_owner binding satisfies a read of any application in the company, and it carries no ability to edit configuration, deploy, read request content or send traffic. Reading request content is an explicit, audited self-grant even for the owner, so an account takeover does not come with a transcript of your customers’ prompts.

Role versions are integers, and a new version of a role cannot widen bindings that already exist. That is why a capability you granted last month still means what you read when you granted it.

Advanced: the action strings behind each role

Authorization is a check against a closed list of actions. These are the strings the API, the audit trail and a preview’s advanced view use; they are not how the grant is presented to the person receiving it.

Role Actions
org_owner org.read, org.manage, members.manage, access.delegate, billing.read, billing.manage, workspace.read, workspace.manage, app.read, app.manage, gateway.read, metadata.read, cost.read
org_admin as org_owner, without billing.read and billing.manage
billing_admin org.read, billing.read, billing.manage, cost.read
workspace_admin workspace.read, workspace.manage, app.read, app.manage, gateway.read, config.edit, deploy, keys.manage, connections.manage, connections.use, tests.run, metadata.read, cost.read, access.delegate
app_developer app.read, gateway.read, config.edit, tests.run, connections.use, metadata.read, cost.read
env_deployer deploy, plus deploy.protected only when the binding lists a protected environment
analyst metadata.read, cost.read, app.read, gateway.read
content_reader content.read
credential_manager keys.manage
connection_manager connections.manage, connections.use
runtime_service inference.invoke, feedback.write

cost.read is the newest verb in that closed set, and six of the eleven roles hold it: org_owner, org_admin, billing_admin, workspace_admin, app_developer and analyst. The five it is withheld from are content_reader, credential_manager, connection_manager, runtime_service and env_deployer. Reading a transcript, holding keys, managing connections, sending traffic and deploying are five jobs that do not need to know what anything cost.

Protected environments, and the one modifier on a grant

Production needs a stronger grant than Development, and naming a thing “production” cannot be what provides it.

Two roles carry a list of environments: env_deployer and runtime_service. A binding without that list reaches no environment, and a binding with it reaches those and no others. Adding a new environment to a workspace grants nobody anything.

deploy.protected is added to an env_deployer binding only when the binding names a protected environment. Production is protected because a field on its row says so, so renaming Development to “Production” confers nothing, and renaming the protected environment doesn’t remove its protection.

What an invitation shows before you send it

An invitation is where a lead either understands the grant or guesses at it, so the review screen states the grant in sentences rather than in action strings.

Invite IT Lead to Acme

Access: Manage User Portal
Can: configure gateways, deploy within granted environments,
     manage application access within the delegated scope
HR Portal: no access
Organization billing: no access
Request content: not included unless separately granted

Send invitation

That preview is the intended copy from the design contract, not a capture from a running screen. Read the negative lines: they are there because “manage this application” is normally heard as more than it grants. The equivalent action strings stay behind an advanced view for the reader who wants them.

An invitation stores a SHA-256 hash of its token rather than the token, and expires within seven days of being sent. Accepting one revalidates three things at that moment: that the inviter still holds delegation over the scope, that the invitation is unexpired, and the precise role versions it named. An invitation written by somebody who has since lost their own access grants nothing.

Who may send which invitation

An invitation does one of two things, and the two are different decisions with different authorities. Offering access to somebody who is already in the company widens a grant, and takes access.delegate at the scope being granted — the workspace_admin lead of IT can do it for IT without asking anybody. An invitation to an address that is not a member yet enlarges the company, because redeeming it writes an organization membership, and that takes members.manage at the organization: org_owner and org_admin hold it, and no other role does.

The split follows removal. members.manage is also what takes a person off the roster, so without it a lead could add somebody they would then be unable to remove.

A lead who tries gets a 403 whose detail names the action rather than the role:

403 forbidden
members.manage is required to invite somebody who is not a member of this
organization yet

There are two ways past it: ask an organization owner or administrator to send that invitation, or grant the access to a colleague who is already a member.

This check fails closed. Deciding whether an address is already a member is a lookup against the company’s own roster. A deployment that cannot answer it — no account directory composed, or a directory that does not respond — treats the invitation as one that would add a member, so it asks for members.manage rather than assuming the invitee is already here.

Teams, invitation previews and explicit role bindings are available in People and access. The preview names the offered role and scope; acceptance checks them again against the inviter’s current authority. Joining a team does not grant the team’s management rights.

Traffic-only access is its own category

The person who installs a key into a running service is the one most often over-granted, because “they need access to the app” gets read as portal access.

A runtime maintainer installs a TokenTriage runtime key into your application and needs nothing else. The key belongs to the application’s service identity, not to a powerful human account. runtime_service grants two things: invoke inference, and write feedback, in the environments its binding lists. It grants no resource management and no request-data reads.

Putting that person on a team roster does not give them the team’s management rights, because rights come from bindings and rosters are not bindings. If they also hold a login, the people screen tells them they hold no management access, instead of showing them an application dashboard of forbidden panels.

That key holds no provider secret either. Applications authenticate to TokenTriage with a TokenTriage runtime key, and TokenTriage reaches the provider through a saved connection. Add a saved provider connection is how that key gets there.

Replacing a runtime key needs the same keys.manage grant and nothing more, because a replacement keeps the service identity and the allowlist it already had: Rotate a runtime key.

A management key carries a person, and the list says which

A management key is what a script or the CLI authenticates with, and it is not a separate identity with permissions of its own. It acts as the person who created it, bounded by that person’s current access, inside one company, and it stops working when it is revoked, when it expires, or when that person leaves. It cannot create a company or a workspace, or accept an invitation, because those operations name no organization.

So the company’s key list answers the question an incident starts with: whose authority is this key spending? Each row names the person it acts as, saying “you” only when the key is the reader’s own, and a revoked row names who revoked it. Requests made with a key are attributed to that person with a key marker in the audit trail, so automation stays distinguishable from a browser session.

A script presents that management key in TOKENTRIAGE_MANAGEMENT_KEY, without a browser cookie. For example, an organization administrator can read the first roster page with the CLI:

tokentriage cloud --base-url "$TOKENTRIAGE_BASE_URL" --org "$TOKENTRIAGE_ORG" --json team list
tokentriage cloud --base-url "$TOKENTRIAGE_BASE_URL" --org "$TOKENTRIAGE_ORG" --json member list

If the response has next_page_token, continue with --page-token before treating its rows as the whole roster. The Python roster example (sdk/python/examples/read_team_rosters.py) and TypeScript roster example (sdk/ts/examples/read_team_rosters.ts) follow every page and read each team’s membership. With the same three environment variables set, their commands are:

python sdk/python/examples/read_team_rosters.py
npx tsx sdk/ts/examples/read_team_rosters.ts

The report’s company count counts each person once. Each team count describes that team’s roster, so a person in both IT and HR appears in both counts. Adding those counts does not give the company headcount, and none of these counts says what content a person may read. The examples print roster metadata only; they do not grant roles, send invitations or request browser cookies.

Content, cost, billing and metadata are four separate grants

The most expensive assumption in a permissions model is that one of these implies another.

Four verbs, four answers. metadata.read says a request happened and how long it took. cost.read says what the traffic cost, per gateway, model, application and person. content.read is the prompt and the completion. billing.read and billing.manage are what the company is charged. No role bundles all four.

metadata.read used to carry the spend figure with it, and it no longer does. Splitting them is what lets a lead grant somebody latency and error rates without granting them the bill, and it is why an invitation preview for an app_developer names the two separately.

So analyst reads what a gateway cost and cannot read what anybody asked it. content_reader reads bodies for one scope and carries no other listing rights, and no cost figure at all. billing_admin reads the invoice and learns nothing about the traffic behind it beyond its cost. Shared team membership grants no content access on its own, which is why one of Acme’s shared developers can work in both workspaces without reading either’s transcripts.

Why a second company cannot read the first

One login belonging to two companies is the case where a leak would be quietest, so the resolution order is worth knowing.

Every request resolves in four steps, in this order:

  1. Confirm active membership in the organization named in the URL. No membership, no further steps.
  2. Take the union of the subject’s direct and team bindings whose scope is the target or one of its ancestors.
  3. Intersect that with the binding’s environment restriction, where it has one.
  4. Check the action the request needs against what remains.

A foreign ID confers nothing at any step, and step 1 means an ID from another company is not even considered. Here is the denial that follows.

An app_developer in Acme pastes an application ID belonging to another company. The answer is 404, not 403, even though the application exists: a 403 would confirm that it does. The same developer asking to deploy to Acme’s own Production without an env_deployer grant naming it gets 403, because that scope is theirs and the action is not.

What happened Status
no session, or an unverified one 401
the scope is unknown, or belongs to another organization 404
the scope is yours and your roles don’t carry the action 403
the state conflicts, such as a slug already taken 409
the revision you held is stale 412
the combination isn’t supported 422

Every one of those answers is a problem document carrying a request_id, which is the value to quote when you ask why.

How long a revocation takes

A permissions page that doesn’t answer this has left the reader guessing at the number that matters during an incident.

Each membership change, binding change and invitation acceptance increments the organization’s authority epoch. Authorization caches key on the subject, the organization and that epoch, and hold for at most 20 seconds. A revocation therefore reaches every reader within that bound, rather than waiting for a session to end.

That is the bound for management reads. Stopping provider dispatch after a connection change is a separate bound with its own measurement, on the provider connections page.

What this page doesn’t cover