Spaces
A space is a group's home on DFOS. A space appears in this API only when its owners have turned on a public profile, and only its public profile and site configuration are served — never its private membership or activity.
GET /v1/spaces— list public spaces.GET /v1/spaces/{space}— fetch one space's full public profile.
The two shapes differ: the listing gives a compact card, and the single fetch adds the site configuration below.
Identifying a space
GET /v1/spaces/{space} accepts a subdomain (home), an entity id
(space_vnzfk7hth9vadc3daahd48), or a protocol DID
(did:dfos:9ctvrdn9vedda7efetrhcdakfh4cr2k). The subdomain is a mutable alias:
a space can change its domain, and a released domain can later belong to a
different space. Store the entity id or the DID; use the subdomain for display
and links. User handles work the same way.
domain is the space's effective subdomain — its custom domain when it has set
one, otherwise the normalized space-{id} form.
Listing spaces
GET /v1/spaces returns public spaces ordered by recent activity,
cursor-paginated (see Conventions). Each item carries id,
did, domain, displayName, description, avatarUrl, verifiedDomain,
links, memberCountSummary, and category when the space has filed one.
curl 'https://api.dfos.com/v1/spaces?joinMode=open&category=music&limit=20'
Four optional filters narrow it. Each is a request parameter; none is encoded in a cursor.
| Parameter | Meaning |
|---|---|
joinMode | closed, open, application, or allowlist. An open enum — handle an unrecognized value gracefully (see the compatibility contract). |
activeWithinDays | Spaces active within that many days. Clamped to 1–365, never rejected. A space with no recorded activity is excluded. |
category | A discovery category. Normalized (trimmed, whitespace collapsed, lowercased) before comparison, so "Music " matches music, and a value that normalizes to empty filters nothing. Pass back a category an item emitted. |
optedIn | true returns only spaces that opted in to the directory, false only those that have not; omit it for both. Opting in is curation, not privacy — a public space that never opted in is still listed unless you ask for the opted-in set. |
Profile links
Both shapes carry links, the space's ordered profile links, empty when it has
set none. Any field but url can be null; render defensively. title and
imageUrl are resolved from the linked page and are absent for a page with no
Open Graph metadata.
| Field | Value |
|---|---|
url | The destination, exactly as the owner entered it. |
label | Owner-curated display label, or null. Fall back to title. |
title | The resolved Open Graph title of the linked page, or null. |
description | An owner-written blurb, or null. |
imageUrl | Cached public CDN URL for the page's Open Graph preview image, or null. |
Verified domain
verifiedDomain is an external domain the space has proven control of, or
null. Distinct from domain, the DFOS subdomain. A value proves control of
that domain at the last check and nothing else — not personhood, endorsement, or
notability. Render the domain itself rather than a checkmark.
It is null when no domain is claimed, the claim has not verified yet, or the
domain now attests a different identity. A domain that stops responding keeps
its value; one that contradicts this identity reads null until it attests it
again.
The proof has two halves you can check yourself: the space's identity chain claims the domain, and the domain publishes the space's DID back. See Protocol discovery and Origin binding.
Site configuration (single fetch only)
GET /v1/spaces/{space} adds:
| Field | Value |
|---|---|
joinMode | How people join (the open enum above). |
siteMode | What the space's public URL presents: join (a landing page) or posts (a posts front page). Also an open enum. |
subscribeEnabled | Whether the space's public email-subscribe form is on. |
memberCountSummary | A worded member count, e.g. "a few dozen members". |
privatePostCountSummary | A worded count of the space's private posts — the complement of what the posts listing serves. A space with none says "no private posts". |
chatMessageCountSummary | A worded count of the messages across all the space's channels, including its private, member-only ones. Direct messages between members are never included. |
eventCountSummary | A worded count of the space's published events. A recurring series counts once. |
mediaUploadCountSummary | A worded count of everything uploaded to the space, including the images it serves publicly — the avatar and header are counted. |
category | The normalized discovery category the space filed itself under. Absent, never null, when it has filed none. Pass it back verbatim to the listing's category filter rather than displaying it. |
headerUrl | A public CDN URL for the space's header image, or null. |
createdAt | When the space was created (ISO 8601 UTC). |
protocol | The space's protocol handles: headOpCid, plus a nested profile (contentId, headOpCid) when the space has a profile content chain. Not on the listing card. |
protocol is absent, never null, when the space has no identity chain, and
profile is absent when the profile is empty. See
Protocol discovery.
Every …CountSummary is a worded sense of scale, never an exact number. Render
each as-is rather than parsing a number out of it. The strings are an open set,
and a zero is spelled out ("no events"), never omitted.
Application questions
applicationQuestions is present only for a public application-mode space
(joinMode: "application") with at least one question configured, and absent
for every other space. Each question carries its question text and whether
answering it isRequired. The list is the same for every caller.
{
"applicationQuestions": [{ "question": "What draws you to this space?", "isRequired": true }]
}
When a space is not found
A space that has no public profile and a space that does not exist both return
the same 404 Not Found — see
Content visibility.
See the interactive reference for the exact response schema of every field, and Posts for a space's public posts.