Permissioned Spaces
Changelog
Latest — Proposal 0016 Alignment
Major restructuring to align with AT Protocol Proposal 0016 (Permissioned Data).
Namespace split
- Protocol routes now live under
com.atproto.space.*(queries, data, credentials) - Management routes now live under
com.atproto.simplespace.*(create/update/delete spaces, membership, config) dev.happyview.space.*endpoints remain as backward-compatible aliases until v3- Invite endpoints remain under
dev.happyview.space.*as HappyView extensions
New terminology
owner_did→authority_did— the DID that controls the space. A separatecreator_didtracks who originally created it.accessMode→mintPolicy— controls who can create permissioned repos:member-list(default),public, ormanaging-appappAllowlist/appDenylist→appAccess— controls third-party app access:open(default) orallowListgetMemberGrant→getDelegationToken— renamed and changed from POST to GET. Returns a delegation token (JWT withtyp: atproto-space-delegation+jwt, ES256K, 60-second TTL)redeemInvite→acceptInvite— renamed for clarity- Space credential
typ— changed fromspace_credentialtoatproto-space-credential+jwt - Space credential TTL — reduced from 4 hours to 2 hours
New access level
read_self— a new membership access level that restricts reads to only the member's own records within the space
New endpoints
com.atproto.space.getRepoState(GET) — returns per-user repo state including LtHash state and signed commitcom.atproto.space.listRepoOps(GET) — returns the record operation log for synccom.atproto.space.listRepos(GET) — lists repos (authors) in a spacecom.atproto.space.getBlob(GET) — retrieves a blob from a spacecom.atproto.space.registerNotify(POST) — registers for write notificationscom.atproto.space.notifyWrite(POST) — pushes a write notificationcom.atproto.space.notifySpaceDeleted(POST) — pushes a space-deleted notificationcom.atproto.simplespace.getConfig(GET) — gets space configuration (mint policy, app access, managing app)com.atproto.simplespace.updateConfig(POST) — updates space configuration
Cryptographic primitives
- LtHash — homomorphic set-hash for per-user repo state. 2048-byte state with 1024 little-endian uint16 lanes using BLAKE3 XOF. Supports insert/remove operations for incremental record tracking.
- Deniable commit signatures — users sign context (space DID + rev + random IKM) rather than content hash, producing a MAC that proves authorship without binding the user to specific content.
Data model changes
- New
happyview_space_repo_statetable — per-user LtHash state + signed commit per space - New
happyview_space_record_oplogtable — ordered record operation log per space - New
happyview_space_notify_registrationstable — write notification registrations - Spaces now use
authority_didandcreator_didinstead ofowner_did mint_policyandapp_accesscolumns replaceaccess_mode,app_allowlist,app_denylist
Breaking changes
- Feature flag disabled response changed from
501 Not Implementedto404withFeatureDisablederror code - Deleting a space now cascades to all associated data (records, members, repo state, oplog, notifications, credentials)
v2.6.0
New endpoints
createRecord: create a record with an auto-generated TID rkey instead of requiring the caller to supply oneapplyWrites: batch multiple create, update, and delete operations in a single request
Optimistic concurrency
swapRecord: optional CID-based concurrency guard onputRecord,deleteRecord, and individual operations withinapplyWrites. Returns409 Conflictwhen the record's current CID doesn't match.swapCommit: optional revision-based concurrency guard onapplyWrites. Asserts the space's current revision before applying any writes. Returns409 Conflicton mismatch.- Spaces now track a
revisionfield (TID) that advances on every write.
Space DID separation
- Spaces now have their own
didfield, distinct from theowner_didof the space creator. For personal spaces these are the same DID; multi-party spaces will have their own DID. - All URI construction and lookups use the space's DID. Ownership checks use
owner_did. - New database migration adds the
didcolumn to thespacestable.
Two-step credential flow
- Replaced the single
getCredentialendpoint with a two-step flow:getMemberGrant: proves membership and returns an HMAC-SHA256 grant (5-minute TTL)getSpaceCredential: exchanges the grant for an ES256 space credential JWT (4-hour TTL)
- Removed the
refreshCredentialendpoint (just repeat the two-step flow)
Bearer auth for space credentials
- Space credentials are now passed as standard
Authorization: Bearer <token>instead of a customX-Space-Credentialheader. HappyView distinguishes credentials from other Bearer tokens by checking the JWTtypheader (space_credential), matching Dan's reference implementation. - No DPoP auth or client key needed when authenticating via space credential.
Endpoint naming
- Space CRUD endpoints renamed to verbNoun format:
space.create→space.createSpace,space.get→space.getSpace,space.list→space.listSpaces,space.update→space.updateSpace,space.delete→space.deleteSpace. - Invite endpoints moved out of the
invite.*sub-namespace:invite.create→space.createInvite,invite.redeem→space.redeemInvite,invite.revoke→space.revokeInvite,invite.list→space.listInvites. - Old endpoint names are still available as legacy aliases and will be removed in a future release.
Bug fixes
- Fixed
WriteOpserde deserialization.swapRecordfields inupdateanddeleteoperations now correctly deserialize from camelCase JSON. - Credential
issclaim now uses the space's DID instead of the owner's DID. SpaceUriparsing updated to usedid(space DID) instead ofowner_did.
v2.5.0
Released 2026-05-05
Initial release of Permissioned Spaces behind the feature.spaces_enabled experimental flag.
Features
- Space CRUD:
create,get,list,update,delete - Record operations:
putRecord,getRecord,listRecords,deleteRecord - Membership management:
addMember,removeMember,listMembers - Invite system:
invite.create,invite.redeem,invite.revoke,invite.list ats://URI scheme for addressing permissioned data- Access model with
default_allow/default_denymodes and app allowlists/denylists - Space credentials for cross-service read access via
X-Space-Credentialheader - Delegation: adding a space as a member transitively grants access to its members
- Lua scripting context includes space metadata (
space.did,space.owner_did,space.type_nsid,space.skey)