Members
Membership determines who can read and write within a space. Members have either read or write access — write implies read.
Adding a member
Only the space owner or a super admin can add members.
const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.addMember", {
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",
did: "did:plc:newmember",
access: "write",
isDelegation: false,
}),
});
interface Member {
id: string;
spaceId: string;
did: string;
access: string;
isDelegation: boolean;
grantedBy: string;
createdAt: string;
}
const data: { member: Member } = await response.json();Input:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
space | string | Yes | The space to add the member to | |
did | string | Yes | DID of the member (or space for delegation) | |
access | string | No | read | read or write |
isDelegation | boolean | No | false | Whether this member is a delegated space |
Response (201):
{
"member": {
"id": "uuid",
"spaceId": "space-uuid",
"did": "did:plc:newmember",
"access": "write",
"isDelegation": false,
"grantedBy": "did:plc:abc123",
"createdAt": "2026-05-09T12:00:00Z"
}
}Removing a member
const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.removeMember", {
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",
did: "did:plc:newmember",
}),
});Listing members
const response = await fetch(
"https://happyview.example.com/xrpc/dev.happyview.space.listMembers?space=ats://did:plc:abc123/com.example.forum/main",
{
headers: {
"X-Client-Key": CLIENT_KEY,
"Authorization": `DPoP ${ACCESS_TOKEN}`,
"DPoP": DPOP_PROOF,
},
},
);
interface ResolvedMember {
did: string;
access: string;
}
const data: { members: ResolvedMember[] } = await response.json();If the space's membershipPublic config is true, this endpoint is accessible without authentication. Otherwise, the caller must be authenticated and be a member.
The response returns the resolved member list — delegation chains are traversed and flattened:
{
"members": [
{ "did": "did:plc:abc123", "access": "write" },
{ "did": "did:plc:newmember", "access": "write" },
{ "did": "did:plc:delegated-user", "access": "read" }
]
}Delegation
A space can be added as a member of another space by setting isDelegation: true. This transitively grants access to all members of the delegated space.
const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.addMember", {
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",
did: "ats://did:plc:org/com.example.team/engineering",
access: "read",
isDelegation: true,
}),
});Delegation chains are resolved up to 10 levels deep. When a user appears in multiple chains, the highest access level wins (write > read).
Example: nested teams
In this example:
- Alice has
writeaccess (via Engineering) - Bob has
writeaccess (via Engineering) - Carol has
readaccess (via Design) - Alice also appears in Design, but
writewins overread