Permissioned Spaces

Overview

Spaces are containers for permissioned data in atproto. Unlike regular public records that live in a user's repo, space records are gated by membership — only members can read or write data within a space.

Concepts

A space is identified by three components:

  • Space DID: the DID of the space's authority. Spaces created through HappyView use HappyView's service identity DID. See Space authority.
  • Type: the space type as an NSID, describing the modality (e.g. a forum, a group chat, a photo album)
  • Space key (skey): a short string differentiating multiple spaces of the same type

These form the space URI: at://<space-did>/space/<type>/<skey>

A space record adds three more components to the URI: the author's DID, the collection NSID, and the record key:

at://<space-did>/space/<type-nsid>/<skey>/<author-did>/<collection>/<rkey>

Feature flag

In HappyView, spaces are gated behind the feature.spaces_enabled instance setting. Enable it in the dashboard under Settings or via the admin API:

const response = await fetch("http://127.0.0.1:3000/admin/settings/feature.spaces_enabled", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ value: "true" }),
});

When disabled, all space endpoints return a 404 error with FeatureDisabled as the error code.

Endpoints

Space endpoints are split across two namespaces:

  • com.atproto.space.*: protocol-level routes (queries, data, credentials, sync)
  • com.atproto.simplespace.*: management routes (create/update/delete spaces, membership)

The dev.happyview.space.* aliases are deprecated and kept until v3. Most endpoints take DPoP authentication or cookie-based session auth. Read and sync endpoints also take a space credential, and the notify endpoints take service auth.

EndpointMethodDescription
com.atproto.simplespace.createSpacePOSTCreate a space
com.atproto.simplespace.getSpaceGETGet a space and its policies
com.atproto.space.listSpacesGETList spaces by membership
com.atproto.simplespace.updateSpacePOSTUpdate a space
com.atproto.simplespace.deleteSpacePOSTDelete a space
com.atproto.simplespace.putMemberPOSTAdd a member or set their access
com.atproto.simplespace.removeMemberPOSTRemove a member
com.atproto.simplespace.listMembersGETList resolved members
com.atproto.space.createRecordPOSTCreate a record (auto-generated rkey)
com.atproto.space.putRecordPOSTWrite a record
com.atproto.space.getRecordGETGet a record
com.atproto.space.listRecordsGETList records
com.atproto.space.deleteRecordPOSTDelete a record
com.atproto.space.applyWritesPOSTBatch write operations
com.atproto.space.getLatestCommitGETGet per-user signed commit
com.atproto.space.getRepoGETExport a user's repo as a CAR file
com.atproto.space.listRepoOpsGETList record operation log entries
com.atproto.space.listReposGETList the space's writer set
com.atproto.space.getBlobGETGet a blob from a space
com.atproto.space.listBlobsGETList the blobs a repo's records reference
com.atproto.space.getDelegationTokenGETGet a delegation token (step 1 of credentials)
com.atproto.space.getSpaceCredentialPOSTExchange it for a space credential (step 2)
com.atproto.space.registerNotifyPOSTRegister a syncer for write notifications
com.atproto.space.unregisterNotifyPOSTWithdraw a registration
com.atproto.space.notifyWritePOSTReport a repo's new commit
com.atproto.space.notifySpaceDeletedPOSTPush a space-deleted notification
dev.happyview.space.createInvitePOSTCreate an invite (HappyView extension)
dev.happyview.space.acceptInvitePOSTAccept an invite (HappyView extension)
dev.happyview.space.revokeInvitePOSTRevoke an invite (HappyView extension)
dev.happyview.space.listInvitesGETList invites (HappyView extension)

com.atproto.simplespace.addMember is a deprecated form of putMember, kept until v3. See Legacy addMember.

Access model

A space's creator administers it: they update and delete the space and manage its members and invites.

Two independent policies decide who can use the space. See Policies.

  • The read policy decides who can obtain a space credential to read the whole space.
  • The write policy decides whose writes the space tracks and forwards to syncers.

Each policy is member-list (the default), public, or managing-app.

App access controls which third-party apps can obtain credentials: open (the default) or an allow list of OAuth client IDs.

Under member-list policies, each member has two flags, read and write, set independently. The creator is automatically added with both. See Members.

Spaces also support delegation: adding another space as a member, which grants its members access to this space.

Alignment with Proposal 0016

HappyView implements atproto Proposal 0016 (Permissioned Data) with some HappyView-specific extensions.

Protocol features implemented

  • Namespace split: com.atproto.space.* for protocol routes, com.atproto.simplespace.* for management
  • Space authority: new spaces use the instance's DID, published with #atproto_space_host and #atproto_space entries
  • Read and write policies: memberListPolicy, publicPolicy, managingAppPolicy
  • App access: open, allowList
  • Member list: putMember with independent read and write flags
  • Delegation tokens: getDelegationToken (GET, 60-second TTL, single use), or tokens signed by the account's own key
  • Space credentials: atproto-space-credential+jwt, ES256, 10-minute TTL, bound to a key with cnf.kid and used with RFC 9421 HTTP Message Signatures
  • Credential revocation: notifyCredentialRevoked sent to hosts of repos in the space
  • Deniable commit signatures: user signs context (space + author + rev + random IKM), not content hash
  • LtHash: homomorphic set-hash (2048-byte state, 1024 uint16 lanes, BLAKE3 XOF)
  • SignedCommit: versioned commit struct (ver: 1) with hash, ikm, sig, mac, rev
  • Record operation log: listRepoOps returns the oplog for sync (values inlined by default, excludeValues to opt out)
  • Latest commit: getLatestCommit returns the signed commit for a user in a space
  • Repo export: getRepo exports a user's repo as a CAR v1 file (signedCommit + DRISL index)
  • Sync: registerNotify by service identifier, notifyWrite in both directions, listRepos from the writer set with a space-wide revision
  • Space-scoped blobs: getBlob, listBlobs

HappyView extensions (not in the protocol spec)

  • Invite system: createInvite, acceptInvite, revokeInvite, listInvites (under dev.happyview.space.*)
  • isDelegation on members: allows spaces to be members of other spaces
  • displayName, description on spaces: human-readable metadata
  • config object: membership_public, plus arbitrary extra fields. records_public is deprecated and not enforced.
  • read_self access: limits a member's reads to their own records

Next steps