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 space's own decentralized identifier (for personal spaces, this is the user's DID)
  • 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: ats://<space-did>/<type>/<skey>

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

ats://<space-did>/<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)
  • com.atproto.simplespace.* — management routes (create/update/delete spaces, membership)

The previous dev.happyview.space.* endpoints remain as backward-compatible aliases until v3. All endpoints require DPoP authentication or cookie-based session auth.

EndpointMethodDescription
com.atproto.simplespace.createSpacePOSTCreate a space
com.atproto.space.getSpaceGETGet a space by URI
com.atproto.space.listSpacesGETList spaces by membership
com.atproto.simplespace.updateSpacePOSTUpdate space metadata
com.atproto.simplespace.deleteSpacePOSTDelete a space
com.atproto.simplespace.getConfigGETGet space configuration
com.atproto.simplespace.updateConfigPOSTUpdate space configuration
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.simplespace.addMemberPOSTAdd a member
com.atproto.simplespace.removeMemberPOSTRemove a member
com.atproto.simplespace.listMembersGETList resolved members
com.atproto.space.getRepoStateGETGet per-user repo state (LtHash + commit)
com.atproto.space.listRepoOpsGETList record operation log entries
com.atproto.space.listReposGETList repos (authors) in a space
com.atproto.space.getDelegationTokenGETGet a delegation token (step 1 of credentials)
com.atproto.space.getSpaceCredentialPOSTGet a space credential (step 2)
com.atproto.space.getBlobGETGet a blob from a space
com.atproto.space.registerNotifyPOSTRegister for write notifications
com.atproto.space.notifyWritePOSTPush a write notification
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)

Access model

Spaces use two independent controls for access:

Mint policy controls who can create permissioned repos in the space:

  • member-list (default) — only members can create repos
  • public — anyone can create repos
  • managing-app — only the managing app can create repos

App access controls which third-party apps can interact with the space:

  • open (default) — any app can access
  • allowList — only explicitly listed apps can access

Individual users access spaces through membership. Members have one of three access levels:

  • write — can read and write data
  • read — can read all data in the space
  • read_self — can only read their own data within the space

Write access implies read. The space creator is automatically added as a write member.

Spaces also support delegation — adding another space as a member, which transitively grants access to all members of the delegated space.

Alignment with Proposal 0016

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

Protocol features implemented

  • Namespace splitcom.atproto.space.* for protocol routes, com.atproto.simplespace.* for management
  • Mint policymember-list, public, managing-app (replaces accessMode)
  • App accessopen, allowList (replaces appAllowlist/appDenylist)
  • Delegation tokensgetDelegationToken (GET, 60-second TTL) replaces getMemberGrant
  • Space credentialsatproto-space-credential+jwt typ, ES256, 2-hour TTL
  • Deniable commit signatures — user signs context (space + rev + random IKM), not content hash
  • LtHash — homomorphic set-hash (2048-byte state, 1024 uint16 lanes, BLAKE3 XOF)
  • Record operation loglistRepoOps returns the oplog for sync
  • Repo stategetRepoState returns LtHash state + signed commit
  • Write notificationsregisterNotify, notifyWrite, notifySpaceDeleted
  • Space-scoped blobsgetBlob
  • Authority DID — spaces use authority_did (not owner_did) with a separate creator_did

HappyView extensions (not in the protocol spec)

  • Invite systemcreateInvite, 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 objectmembershipPublic, recordsPublic, plus arbitrary extra fields
  • read_self access level — restricts reads to the member's own data

Next steps