Govern & operate

Access control

Provision people, put them in groups, control which models they may run, and share data read-only — enforced in the database, not just hidden in the interface.

Open Admin → IAM (superadmins only). Everything here is enforced by row-level security in the database, which is the reason it holds: an agent, an API call and the UI all hit the same rules, so there is no path that quietly bypasses them.

The six tabs

TabWhat you do there
UsersInvite, create, ban, delete; grant/revoke superadmin; manage group membership
GroupsCreate, rename, delete groups and manage members
AccessModel rules and resource grants
BudgetsPer-group spend caps — see Budgets
SettingsPublic-signup toggle, the default model-access policy (allow vs deny), trace retention, and the current superadmin list
SSOSAML identity provider configuration

Roles

There are two: superadmin and everyone else. The account named by ADMIN_EMAIL is the permanent bootstrap superadmin — it keeps the role even if its row is deleted, so you cannot lock yourself out. Superadmins can promote and demote others.

Note

Guardrails you'd expect are enforced: you can't demote, ban or delete the last superadmin, and you can't ban or delete yourself.

Users

Two ways to add someone:

  • Email invitation — they set their own password. Preferred; no credential ever passes through you.
  • Manual with a temporary password — for when email delivery isn't configured or you're provisioning in bulk. Have them change it.

Users can be banned (blocks sign-in, preserves their content) or deleted. Ban first when someone leaves — deletion is not reversible.

Groups

A named set of users. Model rules and resource grants attach to groups, so onboarding becomes "add to Engineering" rather than a dozen individual grants — and offboarding becomes one removal.

Model rules

Control which models a person may run. What "no rules" means is an instance choice — Settings → Default model access:

SituationAllow by default (the default)Deny by default
User has no rules, and no group with rulesUnrestricted — every model availableNo models at all, until a rule allow-lists them
Rules apply (their own or a group's)Allowed = the union of those rulesAllowed = the union of those rules (identical)
SuperadminRules apply to them like anyone elseBypasses entirely — the lock's administrator can't be locked out
Rule is openai + *Every model from that providerEvery model from that provider
Rule is openrouter + openai/*Prefix match — those models onlyPrefix match — those models only

Flipping to deny is safe to stage

The toggle changes nothing for anyone who already has rules, and superadmins are never affected — so the sensible order is: write the allow-lists under this tab, spot-check a non-admin account, then flip. Users denied by the default get a clear "not permitted by the workspace's policy" error, not a hang.

Why it works this way

Union, not intersection. Someone in two groups gets what either allows — because groups are additive grants of capability, and an intersection would mean adding a group could take access away, which nobody expects.

Enforcement is server-side at the point every chat request is dispatched, so it covers the playground, saved agents, swarm nodes, API runs — and public embeds, which execute their owner's stored model for anonymous visitors and are checked against the owner's effective rules on every request. Pickers also filter to allowed models, but that's convenience — the check that matters happens on the request. Disallowed models return a clear error rather than failing obscurely.

Resource sharing

Grant a user or group read-only access to a resource owned by someone else, under Admin → IAM → Access. Ten resource types are grantable, enforced by a database constraint:

TypeWhat the grantee gets
📚 Knowledge baseTheir agents can search it
🗄 SQL data tableQueryable, subject to row filters and column masks
🔑 SecretUsable by reference; the value is never shown
📊 BI dashboardViewable, subject to row filters and column masks
🧮 Semantic modelIts metrics and dimensions become askable
🗂 Data catalog sourceIts crawled tables and profiles become browsable
🤖 LLM key / ☁️ LLM credentialCalls bill to the owner's key
🏢 Database / warehouse connectionQueryable — see below
🔌 App sourceSyncable — see below

Recipients see a Shared badge; edit and delete controls are hidden and writes are blocked by the database regardless.

Because most grants are enforced in row-level security, agent tools inherit them automatically — a shared table becomes queryable by that user's agents with no extra wiring.

Narrowing a grant: row filters and column masks

Sharing a dataset or dashboard does not have to mean sharing all of it. Two optional restrictions can be attached to a grant on a SQL data table or a BI dashboard — the two types that serve rows. Neither applies to the other eight, and the platform refuses to save them there.

RestrictionShapeEffect on the grantee
Row filtera column and a list of valuesThey see only rows where that column matches one of those values
Column maska list of column namesThose columns are removed server-side — from the column list and from every row

A regional lead granted the sales table with a row filter of region ∈ (EMEA) and a column mask of salary can query the table freely, see only EMEA rows, and never receive the salary column in any answer.

Both are applied on the server, before the data leaves it — on stored dashboard snapshots, on live warehouse queries, and on the rows an agent's SQL tool reads. There is no path that applies one and not the others.

Grants add up — they never subtract

Holding two grants can only ever give you more, never less. Two row filters union: a person granted EMEA by one team and APAC by another sees both. A grant carrying no row filter admits every row, and a grant carrying no column mask hides nothing — so an unrestricted grant makes the restricted ones moot. If you need someone narrowed, narrow every grant that reaches them. One deliberate exception: documents synced from a connected service can carry a per-source access scope — "Only me" or "Match source permissions" — which filters retrieval inside a granted knowledge base. That restriction belongs to the source's owner, not to the grant.

A masked column is also unfilterable

Hiding a column is not enough on its own: if a viewer could still filter on it, they could recover the values by narrowing the range and watching which rows come back. Filters naming a masked column are therefore dropped before the query runs, and the response says which ones were dropped rather than silently ignoring them.

A restriction that cannot be checked returns nothing

If a result does not carry the filter's column, the filter cannot be evaluated against it — and an unevaluated filter counts as unsatisfied, so those rows are withheld. Skipping it instead would widen access, which is the opposite of what the person setting it asked for.

Aggregated widgets go empty for a filtered grantee

This is the case you will actually meet. A widget reading SELECT product, sum(revenue) FROM sales GROUP BY product has no region in its output, so a grantee filtered to region ∈ (EMEA) sees an empty widget rather than a global total that ignores their filter. The fix is to project the filter column — group by region, product — so the rows can be vetted. An empty widget is the restriction working, not a broken query.

Shared connections run as their owner

Connections are the exception to the row-level rule, because those rows carry an encrypted credential. There is deliberately no row-level policy granting a recipient access to them — that would let a grantee fetch the ciphertext straight from the API with their own token. Instead the grant is resolved server-side and the row is loaded with the service role, so a grantee gains the use of a connection without ever receiving it. {{secret:NAME}} references resolve as the owner too, never against the grantee's own vault.

A shared app source syncs as its owner, into the owner's datasets — so a grantee re-running a stale sync refreshes the real datasets rather than building a parallel copy under their own account. Sharing the source lets someone keep it healthy; to let them read the resulting data, share those datasets too. Grants are resolved fresh on every call, including scheduled runs, so revoking one takes effect on the next use. Full details in Data sources.

Signup policy and SSO

Invite-only
Turn off public signup and the database itself rejects new accounts that weren't invited or admin-created. Enforced at the trigger, so it also covers OAuth signups.
SSO
Connect a SAML identity provider so people sign in with your corporate directory. Can be made the enforced path.

Worked example — onboarding an analytics team

  1. 1

    Groups → New group

    Name it analytics. Groups are how you avoid granting things person by person.
  2. 2

    Users → Add user → Invite by email

    They set their own password, so no credential passes through you. Repeat for the team, then add each to analytics from their row.
  3. 3

    Access → Model rules → principal: group analytics

    Add openai + gpt-4o-mini and openai + gpt-4o. The team is now restricted to those two; everyone outside the group is still unrestricted unless a rule applies to them.
  4. 4

    Access → Resource grants

    Grant analytics read access to the revenue data table and the Finance policies knowledge base. Their agents can now query both with no further wiring.
  5. 5

    Budgets → new cap, scope group analytics

    Set a monthly USD ceiling before handing out access, not after the first surprise.
  6. 6

    Settings → turn OFF public signup

    Do this before you share the URL. Invitations and admin-created users keep working.

A sensible rollout

  • Sign in as ADMIN_EMAIL and confirm you have the IAM page.
  • Create groups that mirror how people actually work, not the org chart.
  • Turn off public signup before sharing the URL.
  • Set model rules on groups — start permissive, tighten with evidence from Analytics.
  • Share the data collections teams need read access to, rather than duplicating them.
  • Pair rules with budget caps: rules decide what, budgets decide how much.