Skip to content

Access & roles

Access in StoryOS is workspace-wide with three roles, plus space scoping for guests so you can invite a client into exactly one space.

Capability Admin Member Guest
Manage workspace, members, invites, tokens ✅ — —
Edit schema (spaces, databases, fields, relations) ✅ ✅ —
Create / edit / delete records & views ✅ ✅ —
Read records & views (guest: scoped spaces only) ✅ ✅ ✅
Comment ✅ ✅ ✅
  • Members can edit schema in v1 — a small-team trust model. A “lock schema to admins” toggle is planned.
  • The last admin cannot demote or remove themselves.
  • Removed or deactivated users keep their historical authorship in comments and activity.

Members are workspace-wide by design — there’s no per-space member restriction, and none is planned in the near term. If you don’t want someone touching a particular space, the answer isn’t a policy on their membership; it’s a different kind of person entirely: invite them as a guest scoped to only the spaces they need, instead of adding them as a member. A guest at contributor can add and edit records without ever being able to delete anything — the rung exists specifically for “let them contribute, but don’t let them destroy” — and, per the billing section below, viewer/commenter guests cost nothing either way.

Guests are invited to one or more specific spaces:

  • Everything outside their spaces returns 404 — not 403 — so the product never leaks the existence of resources they can’t see.
  • Cross-space relation chips on records they can see render name-only and non-navigable.
  • Guests can read and comment, but aren’t @-mentionable in v1.

This is what lets you drop a client into their project space to check status and leave comments, while every other client’s work stays invisible.

Settings → Billing shows Billable seats — admins and members always count; a guest counts only once their grant reaches contributor or higher. A viewer or commenter guest is free, whatever the total member count on the Members page looks like — pending invites don’t count either, whether they resolve to a paid role or not. The billing page names exactly this rule in its own caption, with a link to Members, precisely so the two numbers can be reconciled rather than left to look like a discrepancy.

An invite that hasn’t been accepted shows a Pending badge on Settings → Members, with Resend invitation and Revoke.

Resending mints a fresh link — the old one stops working. It’s not a reminder email pointing at the same URL; the invite’s token and its 7-day expiry both reset, so a stale copy of the original link left in an old email thread no longer gets anyone in.

Throttled to one resend per minute, per invite — a repeat click inside that window is refused with a plain “wait a minute” message rather than silently sending twice.

Space scoping decides which spaces a guest can see at all. Access grants are the finer tool underneath it, and they come in three scopes:

Scope Reaches
Space Every database in that space
Database One database
Record One record

Each grant carries a role — viewer, commenter, contributor, editor, or creator — the same ladder at every scope, not a second set of names to learn. A grant is exactly one scope; there’s no such thing as a grant that’s both space- and database-scoped.

Where more than one grant applies, the highest wins. A record-scoped editor grant beats a database-scoped viewer grant on that same record, in either order you’d naturally check them.

A record-scoped grant is for “this record and nothing else.” Sharing one record does not extend to other records it links to. A guest whose only access to a database is one or more record grants no longer gets a 404 on the whole database: they can list and query it, and see only the records they were granted. Read paths that were narrowed to match, one phase at a time (ticket #474):

  • record list and query, aggregates, and lookup by record number;
  • relation chips, rollups and lookups — a granted record doesn’t reveal the titles of ungranted records it links to;
  • search, “my work” and recent;
  • backlinks (“Mentioned in”);
  • a record’s activity, version history and comments, and its document (description);
  • sub-items — a sub-item lives in the same database as its parent, but a grant on the parent does not reach it: the parent shows no chip, id or title for it, the sub-item is a 404 by id and absent from lists, and a rollup over sub-items counts only the ones the guest can see. Granting one sub-item widens only that one;
  • activity and mentions — events that name another record (a link added or removed, a mention) are left out of a record-scoped guest’s activity, comment feed and mention chips when the guest can’t read that other record. They are dropped rather than blanked, so no “linked something” row hints that it exists. Admins and members still see the full trail;
  • CSV export;
  • automation runs — the runs list and detail, and re-running an action, check the grant on the record the run fired on, so a guest granted record A can’t re-run something that fired on its sibling B.

A guest who already holds a space or database grant is unaffected — they see what that grant covers, as before — and so is everyone who isn’t a guest. The restriction is enforced by the API, not only hidden in the app. This describes what was built phase by phase against a read-path inventory; if you rely on it to keep one client’s data from another’s, test with a real restricted account.

Space- and database-scoped grants have a real UI: Manage Access. Open it from a space’s own page, or a database’s ⋯ menu. It lists Members (workspace-wide, unconditional) separately from Guests — each guest’s grant level is a dropdown, right there, editable without leaving the dialog. Record-scoped grants have no UI yet — creating or revoking one is still a direct REST API call. No MCP tool creates or removes a grant at any scope; that stays a human decision made directly against the API, not something an agent does on its own. The MCP list_grants tool can still read existing grants, record scope included.

A personal access token (mn_pat_…) acts as its creator — same role, same guest scoping. That’s also an agent’s blast radius: give an MCP agent a token scoped to one guest’s spaces and it can only touch those.