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 withview_objectsreads 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 inlisted_in, and by the teams insee_all, which read every record. The fields are top-leveluser_reforteam_refproperties, or arrays of either, such as an incident’sassignee, a document’sviewers, or a project’steams. Naming a team names its members.
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_objectson an unrestricted type, or anedit_objectsgrant 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.
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:
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.