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 stays a role
Blanket grants name no data set, so they remain ordinary realm roles:
| Role | Grants |
|---|---|
DATAHUB_ADMIN | Read and write every data set, the operator escape hatch |
DATAHUB_DATASET_ALL | Read and write every data set |
DATAHUB_DATASET_READ_ALL | Read every data set |
DATAHUB_DATASET_WRITE_ALL | Write every data set |
Managing the data sets themselves
Creating, updating or deleting a data set itself requires one of the all-data-sets write
grants above, DATAHUB_DATASET_ALL, DATAHUB_DATASET_WRITE_ALL 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.
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, 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.
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. Only a caller holding an all-data-sets grant can see them. |
| 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.
DATAHUB_ADMIN and the _ALL roles should 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 →
- 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