Quickstart: Teams
Invite your team into a workspace, give each role only the Dotabases it needs, and read back who did what.
Everything in Dots happens inside one company — the tenant. This page brings other people into yours and sets what each of them can reach, without opening the whole workspace to everyone.
The model has four parts. Principals are the actors, both human and agent; roles bundle permissions; grants attach access to a single Dotabase; and an append-only audit ledger records the result. Access and Tenancy covers the full plane.
There is no separate membership table: a person in your company is a principal row in your company. Someone who works in two companies has two principal rows tied to one sign-in identity, and authority never crosses between them.
See What You Have
Start from the roles that already exist. Every workspace is seeded with four system roles — Owner, Admin, Editor and Viewer — and you can add your own:
dots access roles list
dots access principals list
The listing gives each role's id and name, and shows the four system roles with an empty permission set, because their contents are code baselines rather than stored rows. Read the catalog to see what those baselines carry:
dots access catalog
That prints all 31 permission atoms and the baseline each system role expands to. The
atoms come in two scopes. -scoped atoms — dotabase.read,
dotabase.rows.create, dotabase.rows.update, dotabase.rows.delete,
dotabase.views.manage, dotabase.schema.manage, dotabase.settings.manage and
dotabase.delete — answer the question "in which Dotabase". Workspace-scoped
atoms cover everything else, from chat.use to access.roles.manage. Two of them,
workspace.provision and cloud_sync.manage, are owner-reserved: they cannot be put
on a custom role at all.
dots access principals list shows people and agents together, because an agent is a
principal like anyone else. Narrow it when you want one kind:
dots access principals list --kind human
Invite People
An invite lands the person on the roles you name, and the accept link is shown once, when you create it:
dots access invites create --email teammate@example.com --role Editor
--role is repeatable and required — an invitation must land on at least one role.
Roles resolve by exact name or by id, so Editor works and a name nobody created does
not.
Creating the invite writes the person into your company straight away, as a principal
with an invited status. They appear in the directory immediately, but nothing their
roles allow is reachable until they accept: any status other than active is denied
before permissions are even consulted.
The token has two properties to plan around. It is shown once — only a hash is stored, so no command prints it again. And it does not expire: an invitation stays good until it is accepted or revoked. Store it the way you would store a credential.
Whether an email also goes out depends on the deployment. An instance with mail configured sends one; a local daemon has no sender, which is why the copyable token is the delivery you can always rely on. Mail is best-effort either way — a send failure never fails the invitation.
To check a token is still good without consuming it, resolve it:
dots access invites resolve --token "$INVITE_TOKEN"
That returns the workspace name and the role, nothing else. It needs no
authentication, so the invitee can run it too, and it answers identically for unknown,
revoked and already-accepted tokens, which stops it being used to probe your
workspace. To withdraw an invitation before it is accepted, dots access invites revoke takes the pending principal's id.
Invite Your Team runs this lifecycle in full, including multi-role invitations and tracking who has not accepted yet.
Make A Role That Fits
If the built-in roles are wider than you want, define one from permission atoms:
dots access roles create --name "Researcher" --description "Reads the corpus, writes notes" --permission dotabase.read
--permission is repeatable, so you build a role by naming each atom it should carry.
Then assign it:
dots access assignments add --principal teammate@example.com --role Researcher
Authority is additive. Roles and grants union together, so a second assignment can only widen what someone can do — it never claws anything back. Narrowing means assigning a thinner role in the first place, not stacking a restrictive one on top. Define Permission Roles goes deeper on building and maintaining custom roles.
Grant One Dotabase At A Time
A grant gives a role or a principal rights to a single Dotabase rather than to the whole workspace. A preset expands into the catalog's atoms for that scope:
dots access grants add --dotabase reading-list --subject role:Researcher --preset editor
dots access grants list
--subject takes role:<name> or principal:<ref>, so one grant can cover everyone
holding a role, or exactly one person. --preset takes a system role key — owner,
admin, editor or viewer — and is filtered to the dotabase-scoped atoms. That
filter is why --preset viewer on a grant resolves to dotabase.read alone rather
than to Viewer's whole baseline.
Use --permission instead of --preset when you want to name atoms yourself; the two
are mutually exclusive, and passing neither is an error. To see what is already
attached to one Dotabase:
dots access grants list --dotabase reading-list
Park An Atom Behind An Approval
--require-approval marks an atom on a grant so that holding it is not the same as
being able to use it:
dots access grants add --dotabase reading-list --subject role:Researcher --permission dotabase.rows.delete --require-approval dotabase.rows.delete
The rule is a union: an atom needs approval only when every source granting it
flags it. If the same person also picks up
dotabase.rows.delete unflagged from another role, the unflagged path wins and they
act without asking. Flag it everywhere it is granted, or it is not gated.
Exercising a flagged atom fails the request rather than quietly queueing it — the
caller gets a 422 APPROVAL_REQUIRED. Deleting a row is the one action that also
parks a pending approval for a decider to act on, with a 72-hour expiry; other flagged
atoms are refused without leaving anything queued. Review what is waiting and decide
it:
dots access approvals list --status pending
dots access approvals approve <approval-id>
dots access approvals deny takes the same argument. Deciding is itself gated, on
access.approvals.decide, and approving re-checks the original permission before it
runs anything.
Suspend Instead Of Removing
When someone leaves, or an account looks compromised, suspend the principal rather than deleting it:
dots access principals update teammate@example.com --status suspended
Suspension is checked before any permission lookup, so every decision denies at once while the principal and everything it did stay in the record. The last active Owner cannot be suspended — the workspace can never be left without one.
Check The Result
The audit ledger records who did what. It is append-only and newest-first:
dots access audit --limit 20
Each entry carries the actor, the event, the target and a detail object, with actor and target names resolved where they can be. There is no update or delete path on the ledger at all — it is written by insert only. Narrow it when it gets busy:
dots access audit --event principal.created --limit 10
--actor, --target, --since, --until and --cursor filter and page the same
way; --limit runs to 200.
Do The Same In The Browser
Every operation above has a screen in the web app. Settings → carries four
tabs: People & Agents, Roles, Approvals — badged with its pending count —
and Audit Log. Inviting from there is the same operation with one difference: the
dialog picks a single role, so a multi-role invitation stays a CLI or API call. The
tab is hidden outright from anyone without access.principals.read.
One Company At A Time
Every command runs against exactly one company. The CLI picks it from --company,
then the DOTS_COMPANY environment variable, then the default you set once:
dots use reading-room
With none of those set, the server resolves it from your memberships. Naming a company
you are not an active member of is denied, never redirected: the request comes back
403 COMPANY_NOT_A_MEMBER, and there is deliberately no quiet fall-back to a company
you do belong to.
Where To Go Next
Agents are principals too, with their own credentials and grants — Quickstart: Agents sets one up. If you are running the workspace on your own infrastructure, see Quickstart: Self-hosting Dots.
Quickstart: Teams · This page in the graph
- mentionstoInvite Your Team
- mentionstoDefine Permission Roles
- mentionstoQuickstart: Self-hosting Dots
- mentionstoQuickstart: Agents
- mentionstoDotabase
- mentionstoAccess
- mentionstoDots
- mentionstoDOT
- referencestoHow Dotabases Work
- referencestoAccess And Tenancy
- referencesfromWhat Is Dots?
- mentionsfromQuickstart: Self-hosting Dots
- mentionsfromQuickstart: Agents
- referencesfromInvite Your Team
- referencesfromAccess Control
- documented byfromWorkspace and Account Settings