docs

Collaboration

Invites

An invite is a shareable token. The owner mints it, passes the string out of band, and the joiner pastes it back to request membership. A guest key is the same mechanism for public read-only access — no request, no approval.

Endpoints

Method Path Purpose
POST /v1/spaces/:spaceId/invites mint an invite; replaces any prior invite
GET /v1/spaces/:spaceId/invites list active invite records
GET /v1/spaces/:spaceId/invites/:recordId one record; 404 invite.not_found
DELETE /v1/spaces/:spaceId/invites revoke all invites
DELETE /v1/spaces/:spaceId/invites/:recordId revoke one invite
POST /v1/spaces/join join with a token (invite or guest, auto-detected)
POST /v1/spaces/:spaceId/guest-key mint the public read-only guest key (owner only, idempotent)
DELETE /v1/spaces/:spaceId/guest-key revoke the guest key and rotate the read key

Mint and share

curl -s -X POST http://127.0.0.1:7001/v1/spaces/$SPACE/invites
{ "spaceId": "bafyrei…", "inviteToken": "5ZHbdx…" }

inviteToken is a base58-packed (spaceId, invitePrivKey). Share the string however you like — a link, a QR code, a message.

any invite create <spaceId>

Join

curl -s -X POST http://127.0.0.1:7001/v1/spaces/join \
  -d '{"inviteToken": "5ZHbdx…", "metadata": {"name": "Bob", "iconCid": "…"}}'
# → 202 SpaceInfo   (status "joining")
any join <invite>

The join is a request: the SDK posts it, writes a joining row into the joiner's space list, and returns 202. The joiner polls GET /v1/spaces/:id/members/me (or watches the space list) for the status to flip to active once the owner accepts with POST …/acl/accept. metadata seeds the name other members see until the joiner's encrypted profile resolves. A malformed or unrecognized token returns 400 invite.invalid.

Until accepted, the joiner can withdraw with POST /v1/spaces/:spaceId/acl/cancel-join.

List and revoke

curl -s http://127.0.0.1:7001/v1/spaces/$SPACE/invites
{ "invites": [
    { "recordId": "bafyrei…", "permission": "none", "inviteToken": "5ZHbdx…" } ] }

Pass recordId to DELETE …/invites/:recordId to revoke one invite, or DELETE …/invites to revoke all in one batch.

any invite list       <spaceId>
any invite get        <spaceId> <recordId>
any invite revoke     <spaceId> <recordId>
any invite revoke-all <spaceId>

Note. inviteToken on a read is the same string the mint returned, recovered from the minting account's synced custody — the ACL record itself carries only the invite public key. It is present only on the devices of the account that minted the invite; every other member gets the row without it. Treat the field as optional and offer "regenerate to get a shareable code".

Guest key: public read-only access

curl -s -X POST http://127.0.0.1:7001/v1/spaces/$SPACE/guest-key
# → { "spaceId": "…", "inviteToken": "…" }

One guest identity per space, idempotent — repeated calls return the same token; owner only. Anyone holding the token joins through the regular POST /v1/spaces/join; the guest kind is encoded in the token and auto-detected. No join request, no approval, no per-user ACL entry: the space loads read-only with ownRole: "guest", and writes return 403 space.read_only.

any invite guest-key        <spaceId>
any invite guest-key-revoke <spaceId>

DELETE …/guest-key removes the guest identity from the ACL and rotates the read key: every guest copy stops receiving new content and flips to status: "guest_revoked" (the local copy stays readable). A later create mints a fresh key; old tokens die permanently.

Guests drop a space with the regular DELETE /v1/spaces/:spaceId. For guest spaces the delete marker is non-terminal — a later join with a valid guest token re-adds the space and re-pulls state from the network.

Why it matters. Revocation is not a flag a server checks on each read. Removing a member or a guest rotates the space's read key, so ciphertext written after the rotation is unreadable to anyone holding only the old key — including the relay that stores it.

Adding by identity instead

When you already know someone's account id you can skip the token round-trip: POST …/acl/add puts them on the ACL directly, and the space surfaces on their side as an invite_pending row they accept or decline.

any invite pending                  # direct-add invites awaiting my approval
any invite accept  <spaceId>
any invite decline <spaceId>