Permissioned Spaces
Invites
Invites let space owners distribute membership tokens without knowing recipients' DIDs in advance.
Creating an invite
Only the space owner or a super admin can create invites.
const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.createInvite", {
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",
access: "write",
maxUses: 10,
expiresAt: "2026-06-01T00:00:00Z",
}),
});
interface CreateInviteResponse {
inviteId: string;
token: string;
access: string;
maxUses: number;
expiresAt: string;
}
const data: CreateInviteResponse = await response.json();Input:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
space | string | Yes | The space this invite is for | |
access | string | No | read | Access level granted on redemption (read or write) |
maxUses | integer | No | unlimited | Maximum number of times the invite can be redeemed |
expiresAt | string (datetime) | No | never | When the invite expires |
Response (201):
{
"inviteId": "uuid",
"token": "a1b2c3d4e5f6...",
"access": "write",
"maxUses": 10,
"expiresAt": "2026-06-01T00:00:00Z"
}Redeeming an invite
Any authenticated user can redeem an invite token to join the space.
const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.redeemInvite", {
method: "POST",
headers: {
"X-Client-Key": CLIENT_KEY,
"Authorization": `DPoP ${ACCESS_TOKEN}`,
"DPoP": DPOP_PROOF,
"Content-Type": "application/json",
},
body: JSON.stringify({
token: "a1b2c3d4e5f6...",
}),
});
interface RedeemInviteResponse {
uri: string;
access: string;
}
const data: RedeemInviteResponse = await response.json();Response (201):
{
"uri": "ats://did:plc:abc123/com.example.forum/main",
"access": "write"
}Redemption fails if:
- The token is invalid (no matching hash found)
- The invite has been revoked
- The invite has reached its
maxUses - The invite has expired
- The user is already a member of the space
Revoking an invite
const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite", {
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",
inviteId: "uuid",
}),
});Revoking an invite prevents future redemptions but does not remove members who already redeemed it.
Listing invites
Only the space owner or a super admin can list invites.
const response = await fetch(
"https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=ats://did:plc:abc123/com.example.forum/main",
{
headers: {
"X-Client-Key": CLIENT_KEY,
"Authorization": `DPoP ${ACCESS_TOKEN}`,
"DPoP": DPOP_PROOF,
},
},
);
interface Invite {
id: string;
access: string;
maxUses: number;
uses: number;
expiresAt: string;
revoked: boolean;
createdBy: string;
createdAt: string;
}
const data: { invites: Invite[] } = await response.json();Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
space | string | Yes | The space to list invites for |
Response:
{
"invites": [
{
"id": "uuid",
"access": "write",
"maxUses": 10,
"uses": 3,
"expiresAt": "2026-06-01T00:00:00Z",
"revoked": false,
"createdBy": "did:plc:abc123",
"createdAt": "2026-05-09T12:00:00Z"
}
]
}The token itself is never returned in list responses — only the invite metadata.