Resource Grants
Vincent Depassier · August 30, 2026
Row filters answer "which rows may this person see". Resource grants answer a different question: may this person take part in that thing?
A chat channel. A board. A call. A document thread. These are objects your app invented, and the platform has no idea what they are — but it still has to gate access to them, because the Event Bus and calling both need to know who may join.
Why this exists
The alternative was tried and it failed in an instructive way.
The realtime hub used to answer "may this end user join this channel?" by reading three specific tables belonging to one specific app. That meant the supposedly generic realtime primitive worked for exactly one product, and silently denied every subscribe for everybody else.
Praxsuite is the platform; an app built on it is a tenant. A tenant's table names have no business inside the platform. Resource grants are the fix: your app declares who may reach its objects, and the platform enforces that declaration without ever learning what a "channel" is.
That is why resourceType is a free-form string you choose — "channel", "board", "dm-thread" — and not an enum we maintain. The platform must never need a code change to learn about a new kind of object.
The shape of a grant
resourceType + resourceId → grantee → access level
"channel" 9f2c8ab1… a person participatePart | Notes |
| Your app's name for this kind of object. Matched case-insensitively, so |
| The specific object — a plain UUID pointing into your own table. No foreign key, deliberately. |
Grantee |
|
Access level |
|
any means every authenticated end user of your app, stated explicitly. It is not the same as no grants at all: the absence of grants means denied, never public.
The API
POST /{workspaceId}/resources/{resourceType}/{resourceId}/grants declare one
PUT /{workspaceId}/resources/{resourceType}/{resourceId}/grants replace the whole list
DELETE /{workspaceId}/resources/{resourceType}/{resourceId}/grants revoke one, or all
GET /{workspaceId}/resources/{resourceType}/{resourceId}/grants read the listPOST https://gateway.praxsuite.com/{workspaceId}/resources/channel/9f2c8ab1-.../grants
Authorization: Bearer sk_live_xxxxxxxx
{ "grantee": "endUser", "endUserId": "…", "accessLevel": "participate" }All four require an API key. An end-user token is refused even though it authenticates perfectly well — otherwise any signed-in person could grant themselves a resource, and the whole access list would be decorative. Declaring access is administrative, so it takes an administrative credential.
The read is key-only for the same reason: the membership list of a private channel is not something a member should be able to enumerate.
Prefer PUT
POST is idempotent per grantee, so re-declaring an existing grant does not duplicate it. But PUT replaces the whole list in one call, and that is almost always what you actually want.
The reason is worth stating: when access changes, re-declaring the resource's full membership is far easier to get right than working out which individual grants to add and remove. A diff computed by each app is a diff each app can get wrong — and getting it wrong means somebody keeps access they should have lost.
So: when a channel's membership changes, send the new membership. Do not compute the delta.
Where they are enforced
The Event Bus, for topics in Per bus mode: the instance segment of the bus key is the resourceId, and joining requires read while publishing requires participate. One resource you declare once is reachable over every transport, with nothing declared twice.
Permission is re-checked on every publish, not only at join — so revoking a grant ejects the peers who no longer qualify rather than letting an open socket outlive the check that admitted it.
From an automation
Most apps do not call this API by hand. The Manage Resource Grants automation node does it as part of the same flow that creates the object, which is the right place: the moment a channel exists is the moment its membership is known.
Guidance
Declare grants when you create the resource, in the same automation. A resource that exists with no grants is a resource nobody can reach — including the person who just made it.
Grant to roles where you can. "Everyone with the Support role" survives staff changes; a list of individual people does not.
Re-assert, do not diff.
PUTthe full list on every change.Remember `manage` is not free. It is the level that lets a grantee change the access list itself.
Next
Event Bus — where Per bus access mode uses these grants.
Gateway Roles — the roles a grant can name.