Appearance
Invitations
Endpoints
POST /api/v1/workspace/members/invite send (auth, admin, gated, quota-enforced)
GET /api/v1/workspace/invitations list pending (auth, admin)
POST /api/v1/workspace/invitations/:id/revoke revoke (auth, admin)
GET /api/v1/invitations/:token public lookup (no auth — token is secret)
POST /api/v1/invitations/:token/accept accept (auth)Invitation fields
typescript
{
workspace_id: ObjectId,
email: string, // lowercase, indexed
role: "owner" | "admin" | "editor" | "author" | "viewer", // default "author"
token: string, // base64url-encoded 32-byte secret
status: "pending" | "accepted" | "revoked" | "expired",
invited_by: ObjectId,
expires_at: Date,
accepted_at: Date | null,
accepted_by: ObjectId | null,
revoked_at: Date | null,
}Flow
- Admin sends
POST /workspace/members/invite { email, role }. Sosyabot mints a 32-bytetokenand emails an acceptance link. - Recipient clicks the link, lands on
/invitation-accept(frontend), which callsGET /invitations/:tokento display invitation details. - If the recipient is logged in (or signs up first), the page calls
POST /invitations/:token/accept. The endpoint movesstatustoaccepted, joins the user to the workspace, and setsaccepted_by.
The token is the only auth needed for the public lookup; treat it as a secret. Revoking an invite (/revoke) flips status to revoked and the token stops working.