How an app talks to a group
The three servers involved, the names to use on the live network, and the six things an app does.
This section is a working guide to building an app against a group host: a group’s own website, an events app, anything that shows or acts on groups. It follows the requests the reference apps make.
Names on the live network
| Setting | Value |
|---|---|
| Host URL | https://host.opensocial.fyi |
| Host DID | did:web:host.opensocial.fyi |
| Service audience for group methods | did:web:host.opensocial.fyi#opensocial |
| Namespace | fyi.opensocial |
Three kinds of server
Every request goes to one of three places. Getting this right is most of the work.
| Server | What it does for your app |
|---|---|
| The user’s own PDS | Signs them in (OAuth). Mints service auth for calling the group host and delegation tokens for reading spaces. Stores every record the user writes, including their records inside a group’s spaces. |
| The group’s host | The group’s PDS: the group DID’s #atproto_pds endpoint points here. Serves the group API (fyi.opensocial.*), exchanges delegation tokens for space credentials, lists who has written into each space, and stores records the group itself writes. |
| Other members’ PDSes | Store their records inside the group’s spaces. To read a space you read from each writer’s PDS. |
A group’s data isn’t in one place. The group’s profile, roles and events live with the group. A member’s RSVP or acceptance lives with that member.
What an app does
- Sign the user in with OAuth, asking for the group permission sets and the host methods you call.
- Find groups: from declarations on the network, the host’s public listing, or the spaces on the user’s own PDS.
- Read a space: delegation token, then space credential, then records from each writer.
- Call group methods with service auth: join, invite, admit, moderate.
- Write records: a member’s own records into a group space, or records as the group.
- Put it together: a group’s events page.
Setting up a project
Spaces, space scopes and the com.atproto.space.* methods exist only in the spaces alpha builds of the atproto packages. Pin them exactly, and override the transitive ones too. Each package also has a plain 0.0.0 release that satisfies a ^0.0.0-spaces-alpha-… range and can’t be installed.
{
"dependencies": {
"@atproto/oauth-client-browser": "0.0.0-spaces-alpha-20260915165437",
"@atproto/lex": "0.0.0-spaces-alpha-20260915165437",
"@atproto/lex-client": "0.0.0-spaces-alpha-20260915165437",
"@atproto/jwk-jose": "^0.2.4"
},
"overrides": {
O0.0.0-spaces-alpha-20260915165437RIDES
}
}
(overrides is npm’s field. With pnpm, put the same map under pnpm.overrides.) @atproto/jwk-jose has no alpha build; the regular release works.
Imports
The snippets in these guides use:
import { BrowserOAuthClient, type OAuthSession } from "@atproto/oauth-client-browser";
import { JoseKey } from "@atproto/jwk-jose";
import { Client } from "@atproto/lex";
import * as com from "./lexicons/com.js"; // generated, see below
Typed calls for com.atproto.space.*
new Client(session).call(com.atproto.space.getDelegationToken, …) needs generated code for those methods. Save the schemas you call into ./lexicons/, one JSON file per NSID (lexicons/com/atproto/space/getDelegationToken.json, …), then generate:
npx lex build --lexicons ./lexicons --out ./src/lexicons
The schemas are in llms-full.txt under “Permissioned-data methods”. Generate only the ones you call: a schema that refers to another (the calendar event refers to community.lexicon.location.*) needs that one in the folder too.
If you’d rather not generate code, call XRPC through the session directly. session.fetchHandler("/xrpc/com.atproto.space.getDelegationToken?space=…") sends an authenticated request to the user’s PDS.
Schemas
Every schema these guides mention, including the com.atproto.space.* methods, is in llms-full.txt under the names the host serves.