MenuFinding groups

Finding groups

How an app learns that a DID is a group, shows it to signed-out visitors, and lists the groups a person is in.

From the network: the declaration

A group that wants to be found publishes fyi.opensocial.declaration (record key self) in its public repo:

{
  "$type": "fyi.opensocial.declaration",
  "meta": "at://did:plc:7x2k…/space/fyi.opensocial.meta/self",
  "createdAt": "2026-09-24T17:37:00.000Z"
}

Watch for that collection on a firehose (a relay, Jetstream, or the host’s own com.atproto.sync.subscribeRepos) and you learn about each group as it appears. An indexer can use fyi.opensocial.declaration as its signal collection, so it only follows repos that are groups.

The declaration only exists while the group’s meta space is public. A group with a private meta publishes none.

For signed-out visitors: listGroups

Nobody can read a space without signing in, even a public one, because every space credential starts with a delegation token from the reader’s own PDS. For signed-out pages, the reference host offers an unauthenticated listing:

GET https://host.opensocial.fyi/xrpc/fyi.opensocial.listGroups
{
  "groups": [
    {
      "did": "did:plc:idmhyhx3335jt2vin45xauu5",
      "handle": "rainshadow-riders.opensocial.fyi",
      "meta": "at://did:plc:idmhyhx3335jt2vin45xauu5/space/fyi.opensocial.meta/self",
      "members": "at://did:plc:idmhyhx3335jt2vin45xauu5/space/fyi.opensocial.members/self",
      "displayName": "Rainshadow Riders",
      "description": "Seattle-based cycling club",
      "url": "https://rainshadow-riders.opensocial.fyi",
      "joinPolicy": "fyi.opensocial.profile#request",
      "avatar": "bafkreifqxrjvscxf6uu23gr34uaugzyobcfdfjjajfialsyvbkorzrgo7q",
      "rules": []
    }
  ]
}
  • Profile and rule fields appear only for groups whose meta space is public. Others return just did, handle, meta and members.
  • avatar is a CID. Show it from https://host.opensocial.fyi/img/<did>/avatar (or /banner), with ?v=<cid> as a cache-buster.
  • A deactivated group comes back with deactivated: true and no profile.

For a signed-in user: “which groups am I in?”

When a member writes their acceptance into a group’s members space, their PDS records that they have a repo in that space. Ask their PDS:

GET <user's PDS>/xrpc/com.atproto.space.listSpaces?type=fyi.opensocial.members

(Through the user’s OAuth session. It needs space:fyi.opensocial.members?authority=*&action=read_self in the scope, or the PDS refuses with ScopeMissingError.) The response is { "spaces": [{ "uri": "at://<group>/space/fyi.opensocial.members/self" }], "cursor": … }.

From a handle to a group

Resolve the handle to a DID in the usual way. The DID document’s #atproto_pds service is the group’s host, and that’s where its spaces and records live. On the reference host, send group methods to https://host.opensocial.fyi with audience did:web:host.opensocial.fyi#opensocial (see Calling group methods).