Data set permissions
Read and write access is granted per data set, through organization groups in your identity provider.
Two properties are worth internalising: read and write are independent, a write grant does not imply read, and denied reads are silently filtered out rather than producing an error.
What permissions cover
Data set permissions gate the four data-bearing entity types: resources, time series, events and files.
| Action | Requires |
|---|---|
| Listing, searching, reading | Read on the entity's data set |
| Receiving subscription deliveries | Read, the same boundary: push delivery is filtered like any other read |
| Creating, updating, deleting | Write on the entity's data set |
| Adding datapoints, uploading files | Write on the target data set |
| Moving an entity to another data set | Write on both the source and the destination |
That last row is the one people trip over. Moving something between data sets is simultaneously a delete and a create as far as access is concerned, so it needs both sides.
How a grant is expressed
Access is administered entirely in Keycloak, or in a customer directory federated into it. There is nothing about access to manage inside DataHub.
Per-data-set access comes from organization groups, named after the data set's external id:
/datasets/<externalId>/read
/datasets/<externalId>/write
Putting somebody in the group is the grant. A member of /datasets/plant_a/read can read
everything in the plant_a data set.
Grants inherit down the hierarchy
A grant on a data set covers every data set beneath it. Data sets form a hierarchy, and access follows it downward.
This is what keeps the model manageable. You grant on a handful of roots rather than on hundreds of individual data sets, and anything created later underneath a granted root is covered automatically.
Read and write are independent
A write grant does not confer read.Somebody who needs both on plant_a must be a member of /datasets/plant_a/read and
/datasets/plant_a/write.
This is deliberate, and it enables the pattern described in users and access: an ingestion service account that can write measurements without being able to read anything back. If those credentials leak, the exposure is limited.
Access to everything is a wildcard group
All-data-sets access follows the same pattern, with * in place of an external id:
/datasets/*/read
/datasets/*/write
A member of /datasets/*/read can read every data set in the organization, including
ones created later. These are ordinary organization groups, sitting in the same group tree
as the per-data-set grants, so a tenant's own administrator can hand them out, and "every
data set" means every data set of that tenant, never another tenant's. The * cannot
collide with a real data set, because
external ids may not contain an asterisk.
Read and write stay independent here too: somebody who needs both must be a member of both groups.
One realm role remains, for the people operating the platform itself:
| Role | Grants |
|---|---|
DATAHUB_ADMIN | Read and write on every data set in every tenant, the operator escape hatch. It is read from the token itself, so operator access keeps working even when the identity provider cannot be reached. |
The realm roles DATAHUB_DATASET_ALL, DATAHUB_DATASET_READ_ALL and
DATAHUB_DATASET_WRITE_ALL no longer exist and are no longer read. A stale assignment
grants nothing: move any remaining holders into the wildcard groups above.
Managing the data sets themselves
Creating, updating or deleting a data set itself requires the all-data-sets write grant,
/datasets/*/write, or DATAHUB_ADMIN. Grants on individual data sets never confer it,
however many of them somebody holds.
The reason is that a data set is the unit access is granted on: renaming one, or moving it in the hierarchy, changes what every existing grant covers.
Because the wildcard group is scoped to one organization, the responsibility can be
delegated per tenant: a tenant's own data steward can hold /datasets/*/write without
gaining anything in any other tenant.
Granting access in Keycloak
In the organization representing the tenant, create /datasets/<externalId>/read or
/datasets/<externalId>/write.
Group membership means nothing until they belong to the organization itself.
That is the whole grant.
Groups that do not match the pattern are ignored, so an organization's group tree can hold whatever else it needs.
Changes take effect within about a minute, the wildcard groups included, rather than waiting for the user's token to expire, so revoking access does not mean asking somebody to sign out and back in.
Why groups rather than roles
Realm roles are global to the realm, but data sets belong to one tenant. A role naming a
data set would mean something different in every tenant, and somebody who belongs to two
tenants would carry it into both. An organization group is scoped to one organization by
construction, and it can be delegated, so a tenant's own administrator can manage their
team's access without realm-wide rights. The same logic is why the all-data-sets grants are
wildcard groups rather than roles: /datasets/*/write in one organization says nothing
about any other.
How denial behaves
Reads and writes are denied differently, and the difference is intentional.
Reads are filtered. List and search results simply omit entities in data sets the caller cannot read. No error is raised. This matches the platform's general contract that missing items are left out, and it avoids leaking the existence of data through error messages.
The practical consequence for support: "I cannot see the series my colleague can see" is almost always a permission, not a bug. Check the group membership before investigating anything else.
Direct reads of a single item return a clear denial, except for files. Fetching a specific resource by id, or starting a graph traversal at one, returns an explicit forbidden response. Files deliberately return "not found" instead, so that a denial cannot be used to confirm that a particular file exists.
Writes fail loudly. Create, update, delete, datapoint insertion and file upload all return an explicit forbidden response naming the data set and the permission that was missing, so the fix is obvious.
If the identity provider cannot be reached, requests are refused with a server error rather than quietly returning nothing. "We could not verify your permissions" is not "you have no permissions", and the platform will not let the two look alike.
Graph traversal is gated on the starting node only
Worth knowing before you rely on data sets to separate sensitive equipment.
Access is checked on the resource a traversal starts from. The connected network returned from there is not itself filtered by data set, so a caller who can read one resource can see resources it connects to even where those sit in data sets the caller cannot read directly.
Do not treat a data set boundary as a barrier inside a connected graph. If two areas must be genuinely separate, separate them in the model, or put them in different tenants.
Entities without a data set
Entities can exist without a data set, typically from an early import, or a create call that did not specify one. What happens then differs by type, and the difference matters:
| Type | With no data set |
|---|---|
| Resources, time series, events | Conservative. Readable only with /datasets/*/read and writable only with /datasets/*/write (or DATAHUB_ADMIN). |
| Files and folders | Public to every authenticated user. No data set means no restriction. |
That file exception is the one to plan around. If you are relying on data sets to control who can read documents, make sure nothing lands without one, and check for existing files that have no data set assigned before you assume the boundary holds.
If data appears to be missing after an import, checking whether it landed without a data set is a good early diagnostic for the other three types. Assigning one brings it under the normal permission model.
Designing a permission scheme
Permissions follow data sets, so the boundary decision is the access decision. Split by who should see it, not by what it is. Designing data set boundaries →
Because grants inherit downward, a group on a top-level data set covers everything under it. Aim for a few groups, not one per data set.
Most people need read on many data sets and write on very few. Reflect that.
The wildcard groups /datasets/*/read and /datasets/*/write, and the DATAHUB_ADMIN
role, should each have a small, reviewable list of holders.
The group names carry the data set they grant, so the group list in your identity provider reads as an access report.
Where policies appear in the console
Each data set has a Policies tab, where lifecycle, access and requirement rules are recorded. Those are documented intent, not enforcement: access comes entirely from the identity-provider groups and roles above, so "who can see this data set" is answered by the group list, and the tab records what should govern it. Policies →
- Using MCP: the grants an AI assistant inherits, and why there is no second permission model
- Data sets: designing the boundaries permissions follow
- Data governance: the wider frame these grants sit in
- Users and access: where roles come from
- Change data capture: where incoming data lands, and its permissions
- Security and compliance: the wider posture