Documentation
On this page

How access works

Each record type says who reads its records, and whatever is attached to a record, its timeseries and its chat channels, follows the record. The database applies both rules to every query.

Access starts from the organization’s permissions: view_objects to read the ontology at all, edit_objects to change it. Owners hold every permission and read every record. The rules below decide which records everyone else reads.

Each record type says who reads its records

A record type, an object type or a team type, has a read rule, set by owners with PUT .../object-types/{type}/access. It is one of two forms:

  • "everyone": every member with view_objects reads every record. This is where a new type starts.
  • {"listed_in": [...], "see_all": [...]}: each record is read by the people and teams named in the fields listed in listed_in, and by the teams in see_all, which read every record. The fields are top-level user_ref or team_ref properties, or arrays of either, such as an incident’s assignee, a document’s viewers, or a project’s teams. Naming a team names its members.
JSON
PUT /v1/ontology/object-types/incident/access
{ "read": { "listed_in": ["assignee"], "see_all": ["<dispatch>", "<supervisors>"] },
  "grants": [{ "team_id": "<dispatch>", "permission": "edit_objects" }] }

Here every technician reads the incidents assigned to them, and dispatch and supervisors read all of them. grants may add edit_objects grants next to a read rule: dispatch may also change incidents. Who may change a record is otherwise usually an action, granted to the teams that need it.

Sharing a record is a write to one of its listing fields, so it is checked like any other write. A record nobody lists is read only by the see-all teams and owners.

A read rule is a short way of writing the type’s grants: the same restricted types, access properties, and assigned grants the API also takes directly, for rules the two forms cannot say, such as an assigned grant for one team only. GET .../access shows read as "custom" for grants like that.

Single properties, such as a salary, can be hidden further from some of the people who read the record; see Restricted properties.

Attached data follows its record

A timeseries or chat type whose settings name an object_type is attached to that record type. Its series, and its channels about a record, are read by exactly the people who read the record: they need view access to the record type, and the record must exist and be visible to them. The attached type’s own view grants play no part in that. So when a machine is listed for one technician, its telemetry and its channel are too, and when the listing changes, so does who reads them.

Whoever reads an attached type’s data also sees the type’s definition, read-only.

Totals follow the same rule. A total across series, such as the power drawn per site, covers only the series the reader reads: a hidden record’s series counts nowhere, not even in the number of series. A property hidden from the reader cannot be used to group or filter them.

Writing to attached data (points of a series about a record, messages, reactions, and members of a channel about one, or a new channel about one) needs edit_objects and one of:

  • edit access to the attached type: edit_objects on an unrestricted type, or an edit_objects grant on a restricted one. This is for collectors, such as an agent that ingests sensor readings for every machine;
  • a listing on the record: the record names the writer, or a team of theirs, in one of the fields its type names in write_attached.
JSON
PUT /v1/ontology/object-types/machine/access
{ "read": { "listed_in": ["technicians"], "see_all": ["<operations>"] },
  "write_attached": { "listed_in": ["technicians"] } }

Here the technicians listed on a machine read its series and channels, post in its channels, and write its readings; operations reads all of it. A write to a series the writer may not write is refused as one they cannot see (422), and a write to a channel about such a record is 403. Private channels still need membership too.

What is not about a record keeps the attached type’s own access: a chat type’s channels about no object, and timeseries types that name no object_type, are read and written through the type’s own grants, as any type is.

Enforced by the database

Postgres decides which rows a query sees, with row level security policies that only narrow what each organization sees. Lists, queries, links, search, live events, MCP tools, and actions all go through them: a record outside the rule is 404, as if it did not exist, and its events and search results never reach the reader. See Organizations and isolation and the verified model of the rules.

Upgrading from earlier versions

Earlier versions read a timeseries or chat type through its own grants, and a timeseries type followed its objects only with inherit_object_access. That switch is gone (it is ignored if sent). The upgrade never lets anyone read more than before:

  • A type that admitted fewer readers than its records (restricted to its own grantees) keeps that as also_requires: true: its readers are the record’s readers who also hold the type’s own view grant.
  • A type that admitted readers its records did not (unrestricted, or with view grants of its own, next to a restricted record type) now follows its records, which narrows it.

Either way, owners see an access_note in the type’s GET .../access, and the server logs it at startup. To let every reader of a record read what is attached to it, an owner clears the rule:

JSON
PUT /v1/ontology/object-types/telemetry/access
{ "restricted": true, "grants": [{ "team_id": "<integrations>", "permission": "edit_objects" }],
  "also_requires": false }

The access PUT replaces the type’s grants, so send the ones to keep with it. also_requires can only be cleared, never set.

Esc
↑ ↓ navigate↵ open