Permissioned Spaces

Managing Spaces

Creating a space

const response = await fetch("https://happyview.example.com/xrpc/com.atproto.simplespace.createSpace", {
  method: "POST",
  headers: {
    "X-Client-Key": CLIENT_KEY,
    "Authorization": `DPoP ${ACCESS_TOKEN}`,
    "DPoP": DPOP_PROOF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "com.example.forum",
    skey: "main",
    displayName: "My Forum",
    description: "A place for discussion",
    mintPolicy: "member-list",
  }),
});
interface CreateSpaceResponse {
  uri: string;
}
const data: CreateSpaceResponse = await response.json();

Input:

FieldTypeRequiredDescription
typestring (NSID)YesThe space type; describes what this space is for
skeystringYesSpace key; differentiates spaces of the same type
displayNamestringNoHuman-readable name
descriptionstringNoDescription of the space
mintPolicystringNomember-list (default), public, or managing-app
appAccessobjectNo{"type": "open"} (default) or {"type": "allowList", "allowed": [...]}
managingAppDidstringNoDID of the application that manages this space
configobjectNoSpace configuration (see below)

Response (201):

{
  "uri": "ats://did:plc:abc123/com.example.forum/main"
}

The creator is automatically added as a write member. Use com.atproto.space.getSpace to retrieve the full space object.

Space configuration

The config object supports:

FieldTypeDefaultDescription
membershipPublicbooleanfalseWhether the member list is visible without authentication
recordsPublicbooleanfalseWhether records are readable without membership

Additional fields are preserved as-is.

Getting a space

const response = await fetch(
  "https://happyview.example.com/xrpc/com.atproto.space.getSpace?space=ats://did:plc:abc123/com.example.forum/main",
  {
    headers: {
      "X-Client-Key": CLIENT_KEY,
      "Authorization": `DPoP ${ACCESS_TOKEN}`,
      "DPoP": DPOP_PROOF,
    },
  },
);
interface GetSpaceResponse {
  uri: string;
  space: Space;
  config: SpaceConfig;
}
const data: GetSpaceResponse = await response.json();

If membershipPublic is false, the caller must be authenticated and be a member (or the authority) to see the space. Non-members receive a 404 Not Found.

Listing spaces

Returns spaces where the authenticated user is a member.

const response = await fetch(
  "https://happyview.example.com/xrpc/com.atproto.space.listSpaces?limit=20",
  {
    headers: {
      "X-Client-Key": CLIENT_KEY,
      "Authorization": `DPoP ${ACCESS_TOKEN}`,
      "DPoP": DPOP_PROOF,
    },
  },
);
interface Space {
  uri: string;
  isOwner: boolean;
}
interface ListSpacesResponse {
  spaces: Space[];
  cursor?: string;
}
const data: ListSpacesResponse = await response.json();

Parameters:

FieldTypeRequiredDefaultDescription
didstringNoauthenticated userFilter by DID
limitintegerNo50Max spaces to return (1-100)
cursorstringNoPagination cursor

Response:

{
  "spaces": [
    {
      "uri": "ats://did:plc:abc123/com.example.forum/main",
      "isOwner": true
    }
  ],
  "cursor": "MjAyNi0wNS0wOVQxMjowMDowMFp8YXRzOi8vZGlkOnBsYzphYmMxMjMvY29tLmV4YW1wbGUuZm9ydW0vbWFpbg"
}

Updating a space

Only the space authority or a HappyView super admin can update a space.

const response = await fetch("https://happyview.example.com/xrpc/com.atproto.simplespace.updateSpace", {
  method: "POST",
  headers: {
    "X-Client-Key": CLIENT_KEY,
    "Authorization": `DPoP ${ACCESS_TOKEN}`,
    "DPoP": DPOP_PROOF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    space: "ats://did:plc:abc123/com.example.forum/main",
    displayName: "Updated Forum Name",
    mintPolicy: "public",
  }),
});

All fields except space are optional. Only provided fields are updated. To clear an optional field, pass null.

Deleting a space

Only the space authority or a HappyView super admin can delete a space.

const response = await fetch("https://happyview.example.com/xrpc/com.atproto.simplespace.deleteSpace", {
  method: "POST",
  headers: {
    "X-Client-Key": CLIENT_KEY,
    "Authorization": `DPoP ${ACCESS_TOKEN}`,
    "DPoP": DPOP_PROOF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    space: "ats://did:plc:abc123/com.example.forum/main",
  }),
});

Getting configuration

Returns the simplespace configuration for a space. Requires admin access (space authority or super admin).

const response = await fetch(
  "https://happyview.example.com/xrpc/com.atproto.simplespace.getConfig?space=ats://did:plc:abc123/com.example.forum/main",
  {
    headers: {
      "X-Client-Key": CLIENT_KEY,
      "Authorization": `DPoP ${ACCESS_TOKEN}`,
      "DPoP": DPOP_PROOF,
    },
  },
);
interface SpaceConfig {
  $type: "com.atproto.simplespace.defs#spaceConfig";
  mintPolicy: string;
  appAccess: object;
  managingApp: string | null;
}
const data: SpaceConfig = await response.json();

Response:

{
  "$type": "com.atproto.simplespace.defs#spaceConfig",
  "mintPolicy": "member-list",
  "appAccess": { "type": "open" },
  "managingApp": null
}
FieldTypeDescription
mintPolicystringmember-list, public, or managing-app
appAccessobject{"type": "open"} or {"type": "allowList", "allowed": ["did:...", ...]}
managingAppstring | nullDID of the application that manages this space

Updating configuration

Updates the simplespace configuration for a space. Requires admin access (space authority or super admin).

const response = await fetch("https://happyview.example.com/xrpc/com.atproto.simplespace.updateConfig", {
  method: "POST",
  headers: {
    "X-Client-Key": CLIENT_KEY,
    "Authorization": `DPoP ${ACCESS_TOKEN}`,
    "DPoP": DPOP_PROOF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    space: "ats://did:plc:abc123/com.example.forum/main",
    mintPolicy: "public",
    appAccess: { type: "allowList", allowed: ["did:web:myapp.example.com"] },
  }),
});

Input:

FieldTypeRequiredDescription
spacestringYesSpace URI
mintPolicystringNomember-list, public, or managing-app
appAccessobjectNo{"type": "open"} or {"type": "allowList", "allowed": ["did:...", ...]}
managingAppstring | nullNoDID of the managing app, or null to clear

All fields except space are optional. Only provided fields are updated. The response returns the updated configuration in the same format as getConfig.