ServicesPubSub

Access Control

Channel access is capability-based. A capability is a resource pattern plus the operations it allows (subscribe, publish), and it travels inside a short-lived minted token - the server stores who may do what, but enforcement reads only the token. Patterns match at dot boundaries: exact (u.{ns}.alerts) or prefix (u.{ns}.*, which covers everything under the namespace but not sibling namespaces). No capability means no access - there is no public channel.

Platform admins and service accounts bypass capability checks entirely on their normal token; everything below is the regular-user path.

Minting

POST /api/v1/pubsub/token (CLI: snug pubsub token) mints a token containing your self-namespace capability (u.{ns}.*, subscribe and publish), every grant you have received, and every room you own. It expires after 600 seconds by default - re-mint before then. Delegated credentials such as API keys cannot mint.

Because capabilities are read from the token, changes to grants and rooms take effect at the next mint. That cuts both ways: a newly received grant does nothing for a token minted before it, and a revoked grant keeps working until the grantee's current token expires.

Grants

A grant shares part of your namespace with another user. The resource must be inside your own namespace - exact or u.{ns}.* - and the grantee must be an existing user id:

snug pubsub grant -r "u.$NS.alerts" -g ZVSnvhZDYZXptZuKwLCz \
  -o subscribe --ttl 3600
{
  "grant_id": "dcLVCpSmAPMmBhGnnAsL",
  "resource": "u.5JY5XUIVDLNWX5WV.alerts",
  "grantee": "ZVSnvhZDYZXptZuKwLCz",
  "granter": "docs-wave",
  "ops": ["subscribe"],
  "expires_at": "2026-08-28T06:09:17.082710Z",
  "created_at": "2026-08-28T05:09:17.083397Z"
}

Without --ttl the grant lives until revoked. Once the grantee re-mints, their token carries the extra capability alongside their own namespace, and a subscribe on your channel succeeds. The grant confers exactly its ops: with subscribe alone, the grantee's publish to that channel still fails with 403 channel_forbidden. All of this was verified live with a second user, including message delivery to the granted subscriber.

Failure modes, reproduced live:

  • Granting a resource outside your namespace: 403 "'u.AAAABBBBCCCCDDDD.x' is not within your channel namespace".
  • Granting to an id that is not a real user: 400 "grantee 'docs4-nobody' is not a known principal".

Listing and revoking:

snug pubsub grants --direction granted    # grants you issued (default)
snug pubsub grants --direction received   # grants made to you
snug pubsub revoke dcLVCpSmAPMmBhGnnAsL   # one grant by id
snug pubsub revoke-all -g ZVSnvhZDYZXptZuKwLCz

revoke-all reports how many it removed ({"revoked": 1}). Only the granter (or an admin) may revoke a grant.

Rooms

A room is a shared channel room.{name} outside any user namespace, claimed first-come:

snug pubsub claim-room docs4-lobby
{ "room": "room.docs4-lobby", "owner": "docs-wave" }

Claiming a room you already own succeeds and returns the same response; someone else claiming it gets 409 "room 'room.docs4-lobby' is already owned by another principal". After a re-mint, the owner's token carries room.docs4-lobby with subscribe and publish, and only the owner may grant it onward - another user attempting to grant it gets 403 "'room.docs4-lobby' is not a room you own". Granting a room works exactly like granting a namespace channel:

snug pubsub grant -r room.docs4-lobby -g ZVSnvhZDYZXptZuKwLCz -o subscribe -o publish

Messages published to a granted room are delivered to the grantee's WebSocket subscription like any other channel (verified live end to end).

Rooms cannot currently be released or transferred - a claim is permanent.

Limits

A principal may hold up to 500 active grants and own up to 500 rooms, and may have at most 100 active grants to any single grantee (exceeding it is a 409). A grant carries between 1 and 8 operations. The token TTL and the namespace-deriving secret are configured in the PubSub CONFIG reference; the endpoints themselves are in the PubSub API reference.

On this page