standards

Permissions

The role-based access model — roles, actions, scopes, and what each endpoint requires.

Authentication tells the platform who you are; authorization decides what you can do. Access is role-based: roles group permissions, and every actor holds one or more roles. An actor's permissions are the union of everything its roles grant.

Three kinds of actor exist: a user, an API key, and an agent — an automated actor that works on records on your behalf, with its own roles and session history.

Anatomy of a permission

A permission grants a set of actions on one target, within one of two scopes:

{ "scope": "object", "target": "contacts", "actions": ["read", "update"] }
  • object scope — business data. The target is an object name ("contacts") or "*" for all objects.
  • system scope — platform resources. The target is a resource name (see below) or "*" for all resources.

Actions

ActionGrants
readFetching data.
createCreating new records or resources.
updateModifying existing records or resources.
deleteSoft-deleting and restoring.
manageAdministrative operations — e.g. permanently purging records.
observeRead-only visibility on agent session timelines. Only meaningful for the session resource.

Actions are independent, not tiers: manage does not imply read, and delete does not imply update. Grant each one you need. "*" in the actions list is shorthand for all of them.

System resources

System-scope targets cover the platform itself rather than your data:

ResourceControls
peopleUser profiles, invitations, roles and permissions.
workspaceWorkspace settings and configuration, including reading the schema.
architectChanging the schema: creating and editing objects and attributes, and applying or rejecting agent-proposed schema plans.
filesThe file store.
documentsThe document drive.
viewsView definitions.
formsForm definitions.
api-keysCreating, listing, and revoking API keys.
connectorsThird-party integrations.
env-varsEnvironment variables.
feature-flagsFeature flags.
auditAudit logs.
sessionAgent sessions (pairs with observe).

What each endpoint requires

Record endpoints check an object-scope permission on the object being accessed. Schema, file, and document endpoints check a system-scope permission:

EndpointsScope / targetAction
List, search, get recordsobject / that objectread
Create records (single or bulk)object / that objectcreate
Update records, attach documentsobject / that objectupdate
Delete, bulk-delete, restore recordsobject / that objectdelete
Purge recordsobject / that objectmanage
Read schema (objects, attributes)system / workspaceread
Create, update, delete schemasystem / architectcreate / update / delete
Read a schema plan (GET /schema/plans/:planId)system / architectread
Apply an approved schema plan (POST /schema/plans/:planId/apply)system / architectupdate
Reject a proposed schema plan (POST /schema/plans/:planId/reject)system / architectupdate
Read a session's schema plan draft (GET /agents/sessions/:sessionId/schema-plan-draft)system / architectread
Discard a session's schema plan draft (DELETE /agents/sessions/:sessionId/schema-plan-draft)system / architectupdate
Files endpointssystem / filesthe matching action
Documents endpointssystem / documentsthe matching action — except record attach, covered by the row above

The matching action is the CRUD action that corresponds to the operation: downloading a file requires read, uploading requires create, and so on. Every system resource not listed here follows the same rule — the operation's CRUD action on its own resource.

A request that exceeds the actor's permissions gets a 403 with the code SCHEMA_FORBIDDEN — see Errors.

Applying or rejecting a schema plan runs as the approver, not the agent

An agent can propose a schema plan, but it holds no power to apply or reject one. Both POST /schema/plans/:planId/apply and POST /schema/plans/:planId/reject run entirely under the calling actor's own identity and architect permission — the request carries only the planId, and the plan's contents are whatever a human already reviewed at proposal time. A plan that has already been applied, rejected, or whose apply failed partway, is refused with a 400 rather than re-run or re-decided — a plan leaves proposed exactly once, into applying (on its way to applied/failed) or rejected, and never moves again. There is no rollback on this platform, so a plan that fails partway leaves whatever already landed in place — GET /schema/plans/:planId is how an approver reads exactly what that was: the plan's status and, when it stopped partway, its outcome (which entries applied, which one failed, and why).

The Architecte agent

The platform ships a built-in system agent, Architecte, that proposes schema changes on request — new objects and attributes, and edits to an object's existing default views — for a human to review. It never applies or rejects anything itself: every plan it proposes goes through the same apply/reject step above, run by a human holding architect/update. Architecte can only edit a default view, never create one — it targets a view by type ("detail" or "list"), never by name, and get_schema shows it each object's default views (and the columns they currently show) so it knows what it would be editing.

A view edit replaces that view's configuration entirely rather than patching it: the plan carries the complete configuration the view should end up with, so anything the agent leaves out is cleared, not kept. That is what makes a proposal readable — the configuration a human approves is exactly the one that lands. For a view on an object that already exists, the proposal must also carry the baseline token issued when Architecte read that view; a view on an object the same plan creates has no baseline yet, and carries no token.

Because a view edit replaces everything, a plan awaiting review also carries a summary of what each view edit actually changes against the view as it stands. It names the changes it can: tabs added, removed, renamed or put in a different order; a tab whose contents moved in some other way; columns added, removed or reordered; the side panel appearing, disappearing, or gaining and losing fields; and the filters applied to the whole view. For a view on an object the plan is creating, it simply says the view is new — there is nothing yet to compare it against.

That list of named changes is not exhaustive, and it does not have to be: anything else the platform cannot name is still reported, as an unspecified change. So a summary that lists nothing means the edit really does leave the view exactly as it is — never that something went unnoticed. It is computed when the plan is proposed — never written by the agent — from the same configuration the baseline token was checked against, so it describes the edit that would land and not a later state of the view.

It is kept short on purpose: past a handful of changes it stops listing them individually and says how many it left out. What it keeps in that case is deliberately lopsided — anything being removed is listed first, so a shortened summary can leave out an addition but never a deletion.

One part of a detail view is exempt from that clearing rule: tabs your own application renders. Architecte has no way to describe or recreate one — nothing in its toolbox lists the screens your code provides — so those tabs are frozen. A proposal is refused if it removes one, adds one that the view does not already have, or changes what such a tab renders, the settings it is handed, or the identifier links use to open it. Changing its displayed title, giving it a different icon, and moving it among the other tabs all remain allowed: those are choices Architecte can see and reason about. The same rule refuses a proposal that puts one of these tabs on a view of an object the plan is creating, since your application has not declared one there.

Architecte reads that baseline with get_default_view, which returns the view's current configuration alongside the token, and is the only thing that issues one. Reading a baseline never creates a view: for an object whose view has never been customized, get_default_view returns the configuration the platform generates for it, which is what that object's view shows today.

The token is checked when the plan is proposed, against the view's configuration at that moment. A proposal is refused with a 400 — before anything is measured or stored — when the token does not match: because the view changed after Architecte read it, because the token has expired (they are short-lived, so a baseline must be read in the same conversation as the plan built from it), because it was issued for a different object, view type, conversation, or workspace, or because it is not a token the platform issued at all. So a view edit that was written against a configuration someone has since changed cannot be proposed, let alone approved — the agent is told to re-read the view and rebuild the edit. Once a proposal is accepted, the plan stores which configuration the approved edit was written against.

That stored configuration is checked again at the moment of approval, and this is what protects you across the gap between the two. Approval can come minutes or days after the proposal, and nothing freezes the view meanwhile — a deploy, another plan, or a colleague can change it. Since a view edit replaces the whole configuration, applying it over a view that has moved would erase whatever arrived in between rather than merge with it. So applying a plan whose view no longer matches the configuration it was written against is refused outright: nothing is written, the plan stays awaiting review rather than being marked failed, and you can ask Architecte for a fresh proposal against the view as it now stands.

Architecte is gated behind the architect/update system permission — an actor without it does not see Architecte in the agent list and cannot open a session with it, the same rule What each endpoint requires describes for every other permission-gated surface.

Its tools are read-only except for the plan surface, which it composes one entry at a time: add_planned_object, add_planned_attributes, set_planned_view and set_planned_removals accumulate a draft scoped to the conversation, add_sample_records populates the preview of it, discard_plan_draft abandons it, request_design_review pauses composition for a chapter approval, and propose_schema_plan — which takes only a summary — submits it for review. Each entry is validated as it lands, so a rejected icon costs that one object rather than the whole plan. It also reads get_schema, get_schema_history (the schema-change audit trail, so it can tell why an attribute exists rather than re-deriving the model each time), get_default_view (the baseline a view edit is authored against), search_icons (the catalogue of icon names a new object may use, so it picks one that exists instead of guessing at a name its plan would be rejected for), and search_records (to see how a model is actually populated). It holds no record-write tool — Architecte cannot create, update, or delete a record, only propose schema changes for a human to approve.

Chapters and design reviews

How much validation a plan gets is proportional to how much it changes. For substantial work — any new object, or a wholesale redesign of a view — Architecte composes in chapters: one object with everything a human needs to judge it, meaning the object itself, both of its default views, and a handful of sample records. At the end of each chapter it calls request_design_review and stops — the conversation is parked until you approve the chapter or ask for changes, and Architecte does not start the next chapter until you answer. Approving objects one at a time is work a human can actually do; approving them all at once at the end buries the first mistake under every chapter built on top of it. When you ask for changes, Architecte revises that same chapter's entries and gates again.

Light modifications — a few attributes on an existing object, a small view tweak — get no gates: Architecte composes and goes straight to propose_schema_plan, because the final plan review is the only validation such a change needs, and a review step in front of it would make you approve the same three fields twice. You outrank the default in both directions: ask to see the work step by step and even a small change is gated; ask to go direct and everything is composed in one pass.

A chapter approval never touches the schema. Whatever rhythm composition took, the plan is still applied in one piece: propose_schema_plan submits the whole draft, and the same apply/reject step described above — run by a human holding architect/update — remains the only thing that changes anything.

The change preview window and sample records

While Architecte composes, a change preview window opens beside the conversation and renders the draft as it stands: proposed objects and views appear as the real list and record views they would become, not as a diff to decode. The window follows the composition — it switches to whichever object Architecte is currently writing to, until you pick one yourself. To make those views readable, each chapter carries 5–8 sample records added with add_sample_records — invented, coherent example data in your language and business domain, never copied from real records. Samples exist only for the preview: they are not part of the plan, and applying the plan never creates them. When a design review is pending, you can approve or request changes from the window's footer or from the card in the conversation — both answer the same gate.

The draft's lifecycle

The draft belongs to the conversation, not to a single reply: it survives Architecte's questions and the design-review pauses, and stays visible in the preview window the whole time. It moves through two states — composing while entries accumulate, then proposed once propose_schema_plan submits it — and it is kept after proposal so the preview still shows what the pending plan would do. It is cleared when the plan is applied or rejected, when Architecte abandons it with discard_plan_draft, or when you dismiss it from the preview window; and once a draft has been proposed, any new entry starts a fresh draft rather than mutating the plan under review.

Two endpoints expose the draft, and the endpoint table above lists their permissions: GET /agents/sessions/:sessionId/schema-plan-draft reads its state (a session with no draft answers { "draft": null } rather than a 404), and DELETE /agents/sessions/:sessionId/schema-plan-draft discards it. On top of the architect permission, both check that the caller can access that agent session — the permission says you may work with drafts at all; the session check says you may see this one, so one architect cannot browse another conversation's work in progress.

Built-in roles

Every workspace starts with three roles:

RoleObject accessSystem access
OwnerAll actions on all objects, including manage.All actions on all resources.
MemberAll actions on all objects, including manage.read on people and workspace; read/create/delete on connectors; read/create/update on files.
Agent Defaultread/create/update/delete on all objects (no manage).read/create/update on documents and files; read on env-vars; observe on session.

Member notably lacks architect (schema changes), api-keys, people administration, and documents — so a Member can upload files and attach them through a record, but not use the standalone document endpoints. Those stay with Owner unless a custom role grants them.

Built-in roles are system-defined and cannot be modified or deleted.

Assigning roles

One assignment is guarded: through the roles API, only an actor who already holds Owner can grant the Owner role. Every other role can be assigned by anyone with permission to administer people. Note that the guard covers that path only — an API key still inherits whatever roles its creator holds.

Custom roles

For finer control — a support role that can only read, an integration key limited to two objects — create a custom role and grant it exactly the permissions it needs. Roles are managed from the People area of your workspace; programmatic administration is available through the SDK's role and permission hooks.

For automations, create one dedicated role per integration. Grant it only the objects and actions that integration touches, then assign it to the integration's API key.