Skip to content

Overview

atproto spaces are containers for permissioned data in atproto. Unlike regular public records that live in a user’s repo, space records are gated: each space’s read and write policies decide who can read or write its data, and by default that is its members.

A space is identified by three components:

  • Space DID: the DID of the space’s authority. Spaces created through HappyView use the instance’s service identity DID, or the creator’s DID when the instance publishes no identity. 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>

In HappyView, atproto spaces are off by default, behind the feature.spaces_enabled instance setting. Enable it in the dashboard under System > Experiments 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" }),
});

While the setting is off, every space endpoint returns a 404 error with FeatureDisabled as the error code, and a script’s happyview.spaces call fails with SPACES_DISABLED. Changing the setting needs the settings:manage permission. While no value is stored, the FEATURE_SPACES_ENABLED environment variable supplies it.

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)

Some of these endpoints also answer under a deprecated dev.happyview.space.* alias. The invite endpoints exist only under dev.happyview.space.* and are not deprecated. Most endpoints take DPoP authentication or cookie-based session auth. Read and sync endpoints also take a space credential, and notifyWrite and notifySpaceDeleted 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.getRepoStateGETEarlier name of getLatestCommit
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. It is still served in v3 alpha; removal is not yet scheduled. See Legacy addMember.

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

With the space inspector on, operators can browse any space’s metadata and members through the admin spaces API without being members. Reading a space’s contents also takes a time-limited grant, logged to the event log. The inspector is off by default.

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.

The space inspector lets an operator read a space’s contents while handling a report. It is off by default, and while it’s off the dashboard hides Spaces.

An operator with settings:manage turns it on with Enable Space Inspector under System > General, or with SPACE_INSPECTOR_ENABLED. The same card sets Maximum Access Length (minutes), the longest access anyone can request. On a HappyProto instance, the platform fixes that length and how long the access records are kept.

With it on, an operator with the spaces:inspect permission requests access from the dashboard:

  1. Open Spaces, then a space, and click Request access on its Records card. To read one account across every space it writes to, enter its DID and click Open account instead.
  2. On a space with members, Access to offers the whole space or one member. A member choice covers that account’s records in every space.
  3. Give a Reason and a Duration, then click Grant access. Access lasts an hour by default, and never longer than the instance allows.

The records appear under a banner showing the reason and the time left. End access ends the grant early; otherwise it expires on its own.

The grant, its reason, and every record list and blob opened under it go to the event log as protected events, which can’t be purged by hand. In Event Logs, a grant’s event lists the reads made under it. The admin spaces API does the same without the dashboard.

A space repository can move from HappyView to its author’s PDS. Each repository goes through three states: polyfill (held by HappyView), migrating, and native (held by the author’s PDS). Migration is gated by the feature.spaces_pds_migration flag. Operators can follow progress with GET /admin/spaces/migration-status.

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

  • 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: the signature covers a context (space + author + rev + random IKM), not the content hash. HappyView signs the repos it holds with its #atproto_space key
  • 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)

Section titled “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, a setting stored with the space, plus arbitrary extra fields. records_public is deprecated.
  • read_self access: limits a member’s reads to their own records