spaces

Anything in your repo is public straight away, which is no good for drafts. A permissioned space is a private area of the same repo, using the same collections and schemas.

Spaces are experimental. They need a PDS running prerelease software: atproto's permissioned-data branch, or the @atproto/pds spaces alpha. Hosted PDSes, including bsky.social, do not support them yet. The API may change.

drafting and publishing

Declare a space with space([...]) and defineSpace, as in your content model. Its collections then have the same API public ones do:

const draft = await airspace.workspace.projects.create({ name: 'Not public yet', category: ref, createdAt: now })

await airspace.workspace.projects.publish(draft.rkey) // copies to the public repo at the same key
await airspace.workspace.projects.publish(draft.rkey, { transform: value => ({ ...value, publishedAt: now }) })
await airspace.workspace.projects.publish(draft.rkey, { ifMatch: draft.cid })

const live = await airspace.workspace.projects.published() // keys that exist in both
await airspace.workspace.supported() // false on most PDSes today

A published copy is identical to its draft, down to the content hash, so ifMatch: draft.cid means "only if nobody has edited the public record since". Pointers between drafts are stored as plain AT URIs, so a published copy keeps them.

Without support, a space call throws SpacesUnsupportedError.

managing a space

airspace.workspace.manage creates a space and sets who can see it. Reading and writing are set separately, each to 'public', 'member-list' or { managingApp: did }:

await airspace.workspace.manage.ensure({ read: 'member-list', write: 'member-list', appAccess: 'open' })
await airspace.workspace.manage.update({ read: 'public' })
await airspace.workspace.manage.members.add(did) // read and write, unless you pass { read, write }
await airspace.workspace.manage.members.list() // [{ did, read, write }]

await airspace.workspace.manage.exists() // is the space there at all?
await airspace.workspace.manage.info() // how is it configured? `null` until `ensure()`

You only need ensure() for a shared space. Writing to your own space creates it but leaves it unconfigured.

Blobs uploaded into a space are currently readable through the public sync.getBlob endpoint (atproto#5435). Don't put images in a space that you'd mind being seen.