Invites
Invites let space creators distribute membership tokens without knowing recipients’ DIDs in advance.
Creating an invite
Section titled “Creating an invite”Only the space’s creator 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: "at://did:web:happyview.example.com/space/com.example.forum/main", access: { read: true, write: true }, maxUses: 10, expiresAt: "2026-06-01T00:00:00Z", }),});interface CreateInviteResponse { inviteId: string; token: string; access: { read: boolean; write: boolean }; maxUses: number; expiresAt: string;}const data: CreateInviteResponse = await response.json();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: "at://did:web:happyview.example.com/space/com.example.forum/main", access: { read: true, write: true }, maxUses: 10, expiresAt: "2026-06-01T00:00:00Z", }),});const data = await response.json();let response = client .post("https://happyview.example.com/xrpc/dev.happyview.space.createInvite") .header("X-Client-Key", client_key) .header("Authorization", format!("DPoP {}", access_token)) .header("DPoP", &dpop_proof) .json(&serde_json::json!({ "space": "at://did:web:happyview.example.com/space/com.example.forum/main", "access": { "read": true, "write": true }, "maxUses": 10, "expiresAt": "2026-06-01T00:00:00Z" })) .send() .await?;let data: serde_json::Value = response.json().await?;body := bytes.NewBufferString(`{ "space": "at://did:web:happyview.example.com/space/com.example.forum/main", "access": { "read": true, "write": true }, "maxUses": 10, "expiresAt": "2026-06-01T00:00:00Z"}`)req, _ := http.NewRequest("POST", "https://happyview.example.com/xrpc/dev.happyview.space.createInvite", body)req.Header.Set("X-Client-Key", clientKey)req.Header.Set("Authorization", "DPoP "+accessToken)req.Header.Set("DPoP", dpopProof)req.Header.Set("Content-Type", "application/json")resp, err := http.DefaultClient.Do(req)curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.createInvite' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP <token>' \ -H 'DPoP: <proof>' \ -H 'Content-Type: application/json' \ -d '{ "space": "at://did:web:happyview.example.com/space/com.example.forum/main", "access": { "read": true, "write": true }, "maxUses": 10, "expiresAt": "2026-06-01T00:00:00Z" }'Input:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
space | string | Yes | The space this invite is for | |
access | object | No | { "read": true, "write": false } | Access granted on acceptance: read and write booleans, both required when access is given |
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": { "read": true, "write": true }, "maxUses": 10, "expiresAt": "2026-06-01T00:00:00Z"}Accepting an invite
Section titled “Accepting an invite”Any authenticated user can accept an invite token to join the space.
const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.acceptInvite", { 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 AcceptInviteResponse { uri: string; access: { read: boolean; write: boolean };}const data: AcceptInviteResponse = await response.json();const response = await fetch("https://happyview.example.com/xrpc/dev.happyview.space.acceptInvite", { method: "POST", headers: { "X-Client-Key": CLIENT_KEY, "Authorization": `DPoP ${ACCESS_TOKEN}`, "DPoP": DPOP_PROOF, "Content-Type": "application/json", }, body: JSON.stringify({ token: "a1b2c3d4e5f6...", }),});const data = await response.json();let response = client .post("https://happyview.example.com/xrpc/dev.happyview.space.acceptInvite") .header("X-Client-Key", client_key) .header("Authorization", format!("DPoP {}", access_token)) .header("DPoP", &dpop_proof) .json(&serde_json::json!({ "token": "a1b2c3d4e5f6..." })) .send() .await?;let data: serde_json::Value = response.json().await?;body := bytes.NewBufferString(`{"token": "a1b2c3d4e5f6..."}`)req, _ := http.NewRequest("POST", "https://happyview.example.com/xrpc/dev.happyview.space.acceptInvite", body)req.Header.Set("X-Client-Key", clientKey)req.Header.Set("Authorization", "DPoP "+accessToken)req.Header.Set("DPoP", dpopProof)req.Header.Set("Content-Type", "application/json")resp, err := http.DefaultClient.Do(req)curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.acceptInvite' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP <token>' \ -H 'DPoP: <proof>' \ -H 'Content-Type: application/json' \ -d '{ "token": "a1b2c3d4e5f6..." }'Response (201):
{ "uri": "at://did:web:happyview.example.com/space/com.example.forum/main", "access": { "read": true, "write": true }}Acceptance 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
Section titled “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: "at://did:web:happyview.example.com/space/com.example.forum/main", inviteId: "uuid", }),});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: "at://did:web:happyview.example.com/space/com.example.forum/main", inviteId: "uuid", }),});let response = client .post("https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite") .header("X-Client-Key", client_key) .header("Authorization", format!("DPoP {}", access_token)) .header("DPoP", &dpop_proof) .json(&serde_json::json!({ "space": "at://did:web:happyview.example.com/space/com.example.forum/main", "inviteId": "uuid" })) .send() .await?;body := bytes.NewBufferString(`{ "space": "at://did:web:happyview.example.com/space/com.example.forum/main", "inviteId": "uuid"}`)req, _ := http.NewRequest("POST", "https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite", body)req.Header.Set("X-Client-Key", clientKey)req.Header.Set("Authorization", "DPoP "+accessToken)req.Header.Set("DPoP", dpopProof)req.Header.Set("Content-Type", "application/json")resp, err := http.DefaultClient.Do(req)curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.revokeInvite' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP <token>' \ -H 'DPoP: <proof>' \ -H 'Content-Type: application/json' \ -d '{ "space": "at://did:web:happyview.example.com/space/com.example.forum/main", "inviteId": "uuid" }'Revoking an invite prevents future redemptions but does not remove members who already redeemed it.
Listing invites
Section titled “Listing invites”Only the space’s creator or a super admin can list invites.
const response = await fetch( "https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=at://did:web:happyview.example.com/space/com.example.forum/main", { headers: { "X-Client-Key": CLIENT_KEY, "Authorization": `DPoP ${ACCESS_TOKEN}`, "DPoP": DPOP_PROOF, }, },);interface Invite { id: string; access: { read: boolean; write: boolean }; maxUses: number; uses: number; expiresAt: string; revoked: boolean; createdBy: string; createdAt: string;}const data: { invites: Invite[] } = await response.json();const response = await fetch( "https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=at://did:web:happyview.example.com/space/com.example.forum/main", { headers: { "X-Client-Key": CLIENT_KEY, "Authorization": `DPoP ${ACCESS_TOKEN}`, "DPoP": DPOP_PROOF, }, },);const data = await response.json();let response = client .get("https://happyview.example.com/xrpc/dev.happyview.space.listInvites") .query(&[("space", "at://did:web:happyview.example.com/space/com.example.forum/main")]) .header("X-Client-Key", client_key) .header("Authorization", format!("DPoP {}", access_token)) .header("DPoP", &dpop_proof) .send() .await?;let data: serde_json::Value = response.json().await?;req, _ := http.NewRequest("GET", "https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=at://did:web:happyview.example.com/space/com.example.forum/main", nil)req.Header.Set("X-Client-Key", clientKey)req.Header.Set("Authorization", "DPoP "+accessToken)req.Header.Set("DPoP", dpopProof)resp, err := http.DefaultClient.Do(req)curl 'https://happyview.example.com/xrpc/dev.happyview.space.listInvites?space=at://did:web:happyview.example.com/space/com.example.forum/main' \ -H 'X-Client-Key: hvc_...' \ -H 'Authorization: DPoP <token>' \ -H 'DPoP: <proof>'Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
space | string | Yes | The space to list invites for |
Response:
{ "invites": [ { "id": "uuid", "access": { "read": true, "write": true }, "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.

