{"info":{"title":"The DFOS API","version":"1.0.0-beta","description":"The external HTTP API for Dark Forest OS — space profiles, public feeds, and\npublic posts.\n\nEvery endpoint today is an anonymous `GET`: no credentials are required or\naccepted, and every response is viewer-independent. Data responses are\n`no-store` (only the generated spec is CDN-cached), so referenced media in a\npublic post comes back as time-limited SIGNED URLs inline — never persist those,\nre-fetch the post for fresh ones (see `urlExpiresAt`). A non-public or missing\nspace returns a 404 that is deliberately indistinguishable from any other — the\nAPI never reveals whether a space it won't serve actually exists.\n\n**Forward-compatibility — clients MUST:**\n- **Tolerate unknown fields.** New response fields are added without a version\n  bump; ignore fields you don't recognize.\n- **Tolerate unknown enum values.** Enums (e.g. `joinMode`, `siteMode`) can grow\n  new members; treat an unrecognized value as an opaque string, not an error.\n- **Treat cursors as opaque tokens.** Pass `nextCursor`/`previousCursor` back\n  verbatim; never parse or construct a cursor.\n- **Treat subdomains as mutable aliases.** A space's subdomain can change or be\n  reassigned; the space `id` and protocol `did` are the canonical, stable\n  identifiers to store.","contact":{"name":"DFOS","url":"https://docs.dfos.com/api"}},"security":[],"externalDocs":{"url":"https://docs.dfos.com/api","description":"DFOS API guides, quickstart, and content-visibility model"},"servers":[{"url":"https://api.dfos.com/v1/","description":"The DFOS API HTTPS endpoint"}],"tags":[{"name":"Spaces","description":"Space profiles, site config, and discovery"},{"name":"Posts","description":"Space feeds and individual public posts"},{"name":"Pages","description":"Space public pages (about, docs, and other durable documents)"},{"name":"Products","description":"Store products with a public purchase page"},{"name":"Topics","description":"World-readable topics within a space"},{"name":"Events","description":"Public calendar events, per space and across all public spaces"},{"name":"Users","description":"Public user profiles"},{"name":"Protocol","description":"Protocol-layer discovery (relay endpoint)"}],"openapi":"3.1.1","components":{"schemas":{"PublicPaginationInput":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"after":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"before":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]}},"additionalProperties":false},"ProtocolDid":{"type":"string","pattern":"^did:dfos:[2346789acdefhknrtvz]{31}$","description":"Protocol DID — `did:dfos:` + 31 chain-derived chars (40 total). The ONLY DID universe the public wire speaks; never the internal 22-char OLTP identifier.","examples":["did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]},"PublicMediaOutput":{"type":"object","properties":{"id":{"type":"string","description":"Media object id — the value an `attachment://<id>` inline body token references. Use it to associate a body token with its entry in `bodyMedia` (required when a body carries more than one inline media item). Media ids already appear verbatim in the body markdown, so this exposes nothing new.","examples":["media_6encc4akrze2ah9kntzd9t"]},"filename":{"type":"string","description":"Original uploaded filename of the media object"},"url":{"type":"string","description":"Resolved URL for the media object. A permanent, unsigned imgix CDN URL for public images; a time-limited SIGNED URL for private media (audio/video/files). When signed, `urlExpiresAt` is present — never persist a signed URL, re-fetch the post for a fresh one."},"contentType":{"type":"string","description":"MIME type of the media object"},"contentLength":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Size of the media object in bytes (absent until upload is finalized)"},"width":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Pixel width (images/video)"},"height":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Pixel height (images/video)"},"blurHash":{"type":"string","description":"Blur-hash placeholder string for progressive image loading"},"alt":{"type":"string","description":"Uploader-authored caption / alt text, when present"},"durationMs":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Playback length in milliseconds (audio/video only)"},"posterUrl":{"type":"string","description":"CDN URL of an extracted poster frame / cover art (audio/video only)"},"playbackUrl":{"type":"string","description":"Streamable MP4 rendition URL (audio/video only). A permanent CDN URL for public media; a time-limited SIGNED URL for private media (in which case `urlExpiresAt` is present)."},"urlExpiresAt":{"type":"string","format":"date-time","description":"When the SIGNED `url` / `playbackUrl` expire (ISO 8601 UTC). PRESENT iff those URLs are time-limited signed URLs (private media); ABSENT means they are permanent (public images). Never persist a signed URL — re-fetch the post to obtain fresh ones."},"waveformPeaks":{"type":"array","maxItems":200,"items":{"type":"integer","minimum":0,"maximum":100},"description":"Amplitude overview for audio — up to 200 integers, each 0–100"}},"required":["id","filename","url","contentType"],"additionalProperties":false,"description":"A resolved media object"},"PublicAuthorOutput":{"type":"object","properties":{"did":{"$ref":"#/components/schemas/ProtocolDid"},"displayName":{"type":"string","description":"Author display name"},"username":{"type":"string","description":"Author username"},"avatarUrl":{"type":"string","description":"Resolved public CDN URL for the author avatar, when present"}},"required":["did"],"additionalProperties":false,"description":"Public author identity"},"SpaceOutput":{"type":"object","properties":{"id":{"type":"string","description":"Space short ID"},"did":{"$ref":"#/components/schemas/ProtocolDid"},"domain":{"type":"string","description":"Effective subdomain — custom domain if set, otherwise the normalized `space-{id}` form. A mutable alias; the `id` and `did` are canonical."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space display name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space description"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the space avatar, or null"},"headerUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the space header/wallpaper image, or null"},"links":{"type":"array","items":{"$ref":"#/components/schemas/PublicProfileLinkOutput"},"description":"Ordered space profile links (may be empty)"},"joinMode":{"$ref":"#/components/schemas/PublicSpaceJoinMode"},"siteMode":{"enum":["join","posts"],"type":"string","description":"What the public space URL presents (join page or posts front page)"},"subscribeEnabled":{"type":"boolean","description":"Whether the public email-subscribe form is enabled for this space"},"memberCountSummary":{"type":"string","description":"Worded member-count summary (e.g. \"a few dozen members\"). Public surfaces deliberately avoid exact counts."},"privatePostCountSummary":{"type":"string","description":"Worded summary of how many posts sit BEHIND the door — live posts this API cannot serve anonymously (e.g. \"a few dozen private posts\", \"no private posts\"). The exact complement of the space's public posts, which the posts listing enumerates precisely as `totalCount`. Approximate worded scale of the space's interior, never an exact count. An opaque display string from an open set — render it, never parse it. Viewer-independent."},"chatMessageCountSummary":{"type":"string","description":"Worded summary of how many chat messages the space's channels hold (e.g. \"hundreds of chat messages\", \"no chat messages\"). Covers every channel bound to the space, including its private ones. Direct and group messages are never space-bound and never counted. Approximate worded scale of the space's interior, never an exact count. An opaque display string from an open set — render it, never parse it. Viewer-independent."},"eventCountSummary":{"type":"string","description":"Worded summary of how many published events the space has scheduled (e.g. \"a few events\", \"no events\"). A recurring series counts once. Approximate worded scale of the space's interior, never an exact count. An opaque display string from an open set — render it, never parse it. Viewer-independent."},"mediaUploadCountSummary":{"type":"string","description":"Worded summary of how many media files have been uploaded to the space (e.g. \"about a hundred media uploads\", \"no media uploads\"). Covers ALL of the space's media, including images it serves publicly — the avatar and header on this response are themselves uploads and are counted. Approximate worded scale of the space's interior, never an exact count. An opaque display string from an open set — render it, never parse it. Viewer-independent."},"applicationQuestions":{"type":"array","items":{"$ref":"#/components/schemas/PublicApplicationQuestionOutput"},"description":"The questions an applicant answers when joining, present ONLY for a public application-mode space (`joinMode: \"application\"`) that has configured at least one question. Absent for every other space. Viewer-independent — the same list for all callers."},"createdAt":{"type":"string","format":"date-time","description":"When the space was created (ISO 8601 UTC)"}},"required":["id","did","domain","displayName","description","avatarUrl","headerUrl","links","joinMode","siteMode","subscribeEnabled","memberCountSummary","createdAt"],"additionalProperties":false,"description":"A public space profile and site configuration"},"PublicApplicationQuestionOutput":{"type":"object","properties":{"question":{"type":"string","description":"The question text an applicant answers"},"isRequired":{"type":"boolean","description":"Whether answering this question is required to apply"}},"required":["question","isRequired"],"additionalProperties":false,"description":"A public space application question"},"PublicSpaceCtaOutput":{"type":"object","properties":{"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space display name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space description"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the space avatar, or null"},"domain":{"type":"string","description":"Effective subdomain — custom domain if set, otherwise the normalized `space-{id}` form. A mutable alias."},"joinMode":{"$ref":"#/components/schemas/PublicSpaceJoinMode"},"subscribeEnabled":{"type":"boolean","description":"Whether the public email-subscribe form is enabled for this space"},"memberCountSummary":{"type":"string","description":"Worded member-count summary (e.g. \"a few dozen members\"). Public surfaces deliberately avoid exact counts."}},"required":["displayName","description","avatarUrl","domain","joinMode","subscribeEnabled","memberCountSummary"],"additionalProperties":false,"description":"Compact space context for a post response (header + join/sign-in CTA)"},"PublicSpaceJoinMode":{"enum":["closed","open","application","paid","allowlist"],"type":"string","description":"How users can join the space"},"SpaceDiscoveryItemOutput":{"type":"object","properties":{"id":{"type":"string","description":"Space short ID"},"did":{"$ref":"#/components/schemas/ProtocolDid"},"domain":{"type":"string","description":"Effective subdomain — custom domain if set, otherwise the normalized `space-{id}` form. A mutable alias; the `id` and `did` are canonical."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space display name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space description"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the space avatar, or null"},"links":{"type":"array","items":{"$ref":"#/components/schemas/PublicProfileLinkOutput"},"description":"Ordered space profile links (may be empty)"},"memberCountSummary":{"type":"string","description":"Worded member-count summary (e.g. \"a few dozen members\"). Public surfaces deliberately avoid exact counts."}},"required":["id","did","domain","displayName","description","avatarUrl","links","memberCountSummary"],"additionalProperties":false,"description":"A public space in the discovery listing"},"SpaceDiscoveryPageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SpaceDiscoveryItemOutput"},"description":"Page of public spaces"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of public spaces"},"PublicPostListItemOutput":{"type":"object","properties":{"id":{"type":"string","description":"Post ID (use as `{postId}` on the single-post route)","examples":["post_6encc4akrze2ah9kntzd9t"]},"slug":{"type":"string","description":"Server-generated URL slug for the post","examples":["building-a-more-generous-internet"]},"format":{"$ref":"#/components/schemas/PublicPostFormat"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Post title","examples":["Building a more generous internet"]},"displayTitle":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Server-derived display label: the title when present, else a short markdown-stripped excerpt of the (above-fold) body, else null. Standardizes the untitled-post fallback; never derived from below-fold content.","examples":["Building a more generous internet"]},"excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Truncated plain-text preview of the post body (markdown-stripped)","examples":["A field guide to shared infrastructure for creative communities."]},"author":{"anyOf":[{"$ref":"#/components/schemas/PublicAuthorOutput"},{"type":"null"}],"description":"Post author"},"cover":{"$ref":"#/components/schemas/PublicMediaOutput","description":"Post cover image, when present"},"upvoteCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of upvotes on the post"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of comments on the post"},"isPinned":{"type":"boolean","description":"Whether the space has pinned this post. The feed is ordered purely by recency (newest first); a client may use this flag to surface pinned posts itself."},"publishedAt":{"type":"string","format":"date-time","description":"When the post was published (ISO 8601 UTC)"},"updatedAt":{"type":"string","format":"date-time","description":"When the post was last updated (ISO 8601 UTC)"},"canonicalUri":{"type":"string","description":"Canonical public web permalink for the post — the space public host (custom domain, else the `space-{id}` subdomain) plus the `/post/{slug}-{id}` path. Same value as on the single-post response."}},"required":["id","slug","format","title","displayTitle","excerpt","author","upvoteCount","commentCount","isPinned","publishedAt","updatedAt"],"additionalProperties":false,"description":"A post in a space public feed"},"PublicPostFormat":{"enum":["short-post","long-post"],"type":"string","description":"Post format: `short-post` (a short, typically untitled note) or `long-post` (a titled article). Open enum — treat an unrecognized value as a generic post.","examples":["long-post"]},"PublicPostOutput":{"type":"object","properties":{"id":{"type":"string","description":"Post ID"},"slug":{"type":"string","description":"Server-generated URL slug for the post","examples":["building-a-more-generous-internet"]},"format":{"$ref":"#/components/schemas/PublicPostFormat"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Post title","examples":["Building a more generous internet"]},"displayTitle":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Server-derived display label: the title when present, else a short markdown-stripped excerpt of the (above-fold) body, else null. Standardizes the untitled-post fallback; never derived from below-fold content.","examples":["Building a more generous internet"]},"excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Truncated plain-text preview of the post body (markdown-stripped)","examples":["A field guide to shared infrastructure for creative communities."]},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Post content body (markdown)"},"cover":{"$ref":"#/components/schemas/PublicMediaOutput","description":"Post cover image"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/PublicMediaOutput"},"description":"Post attachments. Includes private media (audio/video/files), which carry time-limited signed `url` / `playbackUrl` values (see `urlExpiresAt`)."},"bodyMedia":{"type":"array","items":{"$ref":"#/components/schemas/PublicMediaOutput"},"description":"Media objects referenced inline in the post body markdown. The body carries `attachment://<id>` tokens; resolve each against this array by its `id`. Always present (empty array when none)."},"author":{"anyOf":[{"$ref":"#/components/schemas/PublicAuthorOutput"},{"type":"null"}],"description":"Post author"},"topics":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Topic ID"},"name":{"type":"string","description":"Topic name"}},"required":["id","name"],"additionalProperties":false},"description":"Topics this post is assigned to"},"links":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"The original shared URL represented by this link card","examples":["https://example.com/field-notes"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved page title, or null when unavailable","examples":["Field Notes"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved page description, or null when unavailable"},"siteName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved site name, or null when unavailable","examples":["Example"]},"imageUrl":{"type":"string","description":"Permanent public CDN URL for the cached link preview image, when available"},"faviconUrl":{"type":"string","description":"Permanent public CDN URL for the cached site favicon, when available"},"sortOrder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Zero-based order of first appearance in the post body"}},"required":["url","title","description","siteName","sortOrder"],"additionalProperties":false,"description":"An extracted link card from a public post body"},"description":"Link cards extracted from URLs in the post body, in appearance order. Absent when the post has no links."},"entityLinks":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"Normalized absolute internal URL as it appears in the served post body"},"label":{"type":"string","description":"Viewer-independent display label for the entity"},"kind":{"type":"string","description":"Entity kind. Current values: `post`, `space`, or `event`. Open enum — clients must render unknown values as ordinary links."}},"required":["url","label","kind"],"additionalProperties":false,"description":"Resolved label for an internal entity link in a public post body"},"description":"Internal entity links derived from the served (folded) body. Viewer-independent; post and space labels resolve at the anonymous/public floor, while events must be published and space-visible in a space whose public profile is enabled. Omitted when no internal links resolve."},"folded":{"type":"boolean","description":"True when the body was truncated at a fold marker — only the above-fold teaser is present (the public surface is always the non-reader projection). Absent when the post has no fold."},"canonicalUri":{"type":"string","description":"Canonical public web permalink for the post — the space public host (custom domain, else the `space-{id}` subdomain) plus the `/post/{slug}-{id}` path."},"upvoteCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of upvotes on the post"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of comments on the post"},"publishedAt":{"type":"string","format":"date-time","description":"When the post was published (ISO 8601 UTC)"},"updatedAt":{"type":"string","format":"date-time","description":"When the post was last updated (ISO 8601 UTC)"}},"required":["id","slug","format","title","displayTitle","excerpt","body","attachments","bodyMedia","author","topics","upvoteCount","commentCount","publishedAt","updatedAt"],"additionalProperties":false,"description":"Full public post content"},"PublicPostPageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicPostListItemOutput"},"description":"Page of public posts"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of public posts"},"PublicPageSummaryOutput":{"type":"object","properties":{"id":{"type":"string","description":"Stable page entity id. Store this identifier; the slug may change.","examples":["page_6encc4akrze2ah9kntzd9t"]},"slug":{"type":"string","description":"Mutable page slug alias (accepted as `{page}` on the single-page route)","examples":["about"]},"title":{"type":"string","description":"Page title","examples":["About this space"]},"sortOrder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Flat ordering within the space (ascending); the list is pre-sorted"},"updatedAt":{"type":"string","format":"date-time","description":"When the page was last updated (ISO 8601 UTC)"}},"required":["id","slug","title","sortOrder","updatedAt"],"additionalProperties":false,"description":"A page in a space public page list"},"PublicPageOutput":{"type":"object","properties":{"id":{"type":"string","description":"Stable page entity id. Store this identifier; the slug may change.","examples":["page_6encc4akrze2ah9kntzd9t"]},"slug":{"type":"string","description":"Mutable page slug alias","examples":["about"]},"title":{"type":"string","description":"Page title","examples":["About this space"]},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page content body (markdown). Inline media appears as `attachment://<mediaId>` tokens — resolve each against `bodyMedia` by id."},"cover":{"$ref":"#/components/schemas/PublicMediaOutput","description":"Page cover image, when present"},"bodyMedia":{"type":"array","items":{"$ref":"#/components/schemas/PublicMediaOutput"},"description":"Media objects referenced inline in the page body markdown"},"sortOrder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Flat ordering within the space (ascending)"},"updatedAt":{"type":"string","format":"date-time","description":"When the page was last updated (ISO 8601 UTC)"}},"required":["id","slug","title","body","sortOrder","updatedAt"],"additionalProperties":false,"description":"Full public page content"},"PublicPagePageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicPageSummaryOutput"},"description":"Page of public pages"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of public pages"},"PublicTopicOutput":{"type":"object","properties":{"id":{"type":"string","description":"Topic ID (use as the `topic` filter on the posts listing)","examples":["topic_6encc4akrze2ah9kntzd9t"]},"name":{"type":"string","description":"Topic name","examples":["Announcements"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Topic description","examples":["News and updates from the community."]}},"required":["id","name","description"],"additionalProperties":false,"description":"A public topic"},"PublicEventType":{"enum":["update","attend"],"type":"string","description":"What the event is: `attend` (people show up — a session, a call, a party) or `update` (the space will publish something on this date). Open enum — treat an unrecognized value as a generic event.","examples":["attend"]},"PublicEventMode":{"enum":["call","place","chat"],"type":"string","description":"How an `attend` event happens: `call` (video/audio call), `place` (a physical location), or `chat` (in the space itself). Presentational only, and null on `update` events. Open enum — treat an unrecognized value as unspecified.","examples":["place"]},"PublicEventSpaceOutput":{"type":"object","properties":{"id":{"type":"string","description":"Space short ID — canonical and stable.","examples":["space_6encc4akrze2ah9kntzd9t"]},"did":{"$ref":"#/components/schemas/ProtocolDid"},"domain":{"type":"string","description":"Effective subdomain — custom domain if set, otherwise the normalized `space-{id}` form. A mutable alias; the `id` and `did` are canonical."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space display name"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the space avatar, or null"}},"required":["id","did","domain","displayName","avatarUrl"],"additionalProperties":false,"description":"The space an event belongs to"},"PublicEventOutput":{"type":"object","properties":{"id":{"type":"string","description":"Event ID. Identifies the EVENT; a recurring event yields one item per occurrence, so use `(seriesId, recurrenceId)` to address a specific one.","examples":["evt_6encc4akrze2ah9kntzd9t"]},"seriesId":{"type":"string","description":"The series this occurrence belongs to — the same value as `id` today. Half of the occurrence identity pair.","examples":["evt_6encc4akrze2ah9kntzd9t"]},"recurrenceId":{"type":"string","description":"RFC 5545 RECURRENCE-ID: this occurrence's stable identity within its series — its local wall-time (`YYYY-MM-DDTHH:MM:SS`) or, for an all-day event, its date (`YYYY-MM-DD`). Stable across DST; never identify an occurrence by its resolved UTC instant.","examples":["2026-08-15T19:00:00"]},"space":{"$ref":"#/components/schemas/PublicEventSpaceOutput"},"title":{"type":"string","description":"Event title","examples":["Listening session"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Event description (plain text), or null"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text location as the organizer wrote it, or null"},"type":{"$ref":"#/components/schemas/PublicEventType"},"mode":{"anyOf":[{"$ref":"#/components/schemas/PublicEventMode"},{"type":"null"}],"description":"Attend-event flavor; null when unspecified or on an `update` event."},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Waypoint URL the organizer attached (a call link, a ticket page), or null. Arbitrary organizer-supplied content — treat it as untrusted."},"isAllDay":{"type":"boolean","description":"True for a date-based event: `startDate`/`endDate` carry the dates and every instant field is null. False for a timed event, where the reverse holds."},"startsAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"This occurrence's resolved start instant (ISO 8601 UTC). Null for an all-day event.","examples":["2026-08-16T00:00:00.000Z"]},"endsAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"This occurrence's resolved end instant (ISO 8601 UTC). Null for an all-day event or an open-ended one; equal to `startsAt` for a point-in-time event."},"startDate":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"All-day only: this occurrence's start date (`YYYY-MM-DD`). Null for a timed event.","examples":["2026-08-15"]},"endDate":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"All-day only: EXCLUSIVE end date (`YYYY-MM-DD`) — null means a single day. Null for a timed event."},"localStart":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Timed only: this occurrence's start as local wall-clock time in `timeZone` (`YYYY-MM-DDTHH:MM:SS`) — the time the organizer actually authored. Equal to `recurrenceId` for a timed occurrence. Null for an all-day event."},"timeZone":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Timed only: the IANA zone the event was authored in — the zone `localStart` is expressed in. Null for an all-day event.","examples":["America/Chicago"]},"interestCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many members have marked interest in the event (its RSVP count). Members only — this API cannot RSVP."},"cover":{"$ref":"#/components/schemas/PublicMediaOutput","description":"The cover image to render for THIS occurrence, when present: the occurrence's own cover art if the organizer set one, otherwise the event's. Always a public image with a PERMANENT unsigned URL — no `urlExpiresAt`, safe to hotlink while the event references it."}},"required":["id","seriesId","recurrenceId","space","title","description","location","type","mode","url","isAllDay","startsAt","endsAt","startDate","endDate","localStart","timeZone","interestCount"],"additionalProperties":false,"description":"One occurrence of a public event"},"PublicEventPageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicEventOutput"},"description":"Page of public event occurrences"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of public event occurrences"},"PublicTopicPageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicTopicOutput"},"description":"Page of public topics"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of public topics"},"PublicProductOutput":{"type":"object","properties":{"id":{"type":"string","description":"Stable store-product id. Store this identifier; the slug may change.","examples":["sprod_6encc4akrze2ah9kntzd9t"]},"slug":{"type":"string","description":"The product's public purchase-page slug (the `/p/{slug}` segment on the space site). Mutable — changing it breaks old links with no redirect; store the `id`.","examples":["softr-ticket"]},"name":{"type":"string","description":"Product name","examples":["SOFTR Ticket"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Seller-authored plain-text description. Newlines are significant; render with preserved whitespace."},"kind":{"enum":["digital","physical"],"type":"string","description":"What the product delivers. Open enum — treat an unrecognized value as an opaque string."},"image":{"$ref":"#/components/schemas/PublicMediaOutput","description":"Hero image, when the product has one. Always a PUBLIC, permanently-hosted CDN image — this endpoint never emits a signed or expiring URL."},"prices":{"type":"array","items":{"type":"object","properties":{"type":{"enum":["one_time","pwyw"],"type":"string","description":"Pricing model. `one_time` is a fixed price; `pwyw` lets the buyer choose an amount at or above the floor. Open enum — tolerate unrecognized values."},"amountCents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"For `one_time`, the price. For `pwyw`, the MINIMUM the buyer may pay (0 means a free floor).","examples":[1000]},"suggestedCents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Suggested amount for a `pwyw` price, to prefill a buyer amount input. Absent when there is no suggestion, and always absent for `one_time`.","examples":[1500]},"currency":{"type":"string","description":"ISO 4217 currency code, lowercase","examples":["usd"]}},"required":["type","amountCents","currency"],"additionalProperties":false,"description":"A purchasable price tier for a public product"},"description":"Every purchasable price tier. A store product carries exactly one today, but the array is the forward-compatible shape."},"available":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Remaining stock, or null when the product has unlimited stock. Advisory only — availability is re-checked at purchase."},"isSoldOut":{"type":"boolean","description":"True when limited stock is exhausted (`available` <= 0)."},"purchasable":{"type":"boolean","description":"Whether a purchase can be STARTED right now. False when the platform payment rail is paused or the product has no completable price tier — render an unavailable state rather than a dead buy button. Independent of `isSoldOut`, which has its own message."}},"required":["id","slug","name","description","kind","prices","available","isSoldOut","purchasable"],"additionalProperties":false,"description":"A store product with a public purchase page"},"PublicProductPageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicProductOutput"},"description":"Page of public products"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of public products"},"PublicProfileLinkOutput":{"type":"object","properties":{"url":{"type":"string","description":"The link URL as the profile owner entered it"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Owner-curated display label, or null (falls back to the OG title)"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved Open Graph title of the linked page, or null"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Owner-curated short blurb for the link, or null"},"imageUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the cached OG preview image, or null"}},"required":["url","label","title","description","imageUrl"],"additionalProperties":false,"description":"A link on a public user profile"},"PublicUserOutput":{"type":"object","properties":{"id":{"type":"string","description":"The user's identity entity id (`identity_…`) — a stable, URL-safe identifier, accepted anywhere `{user}` is. The canonical profile-URL identifier when the user has no handle."},"did":{"$ref":"#/components/schemas/ProtocolDid"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's handle, or null if unset. A MUTABLE alias — a handle can change or be released; the `id` and `did` are the canonical, stable identifiers to store."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Display name, or null"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the avatar, or null"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Profile bio / description, or null"},"links":{"type":"array","items":{"$ref":"#/components/schemas/PublicProfileLinkOutput"},"description":"Ordered profile links (may be empty)"},"spaces":{"type":"array","items":{"$ref":"#/components/schemas/PublicUserSpaceOutput"},"description":"The user's public space memberships: every pinned space (in slot order) followed by up to 20 more, most recently joined first. May be empty. Use `GET /users/{user}/spaces` for the complete, paginated list."}},"required":["id","did","username","displayName","avatarUrl","bio","links","spaces"],"additionalProperties":false,"description":"A public user profile"},"PublicUserListItemOutput":{"type":"object","properties":{"id":{"type":"string","description":"The user's identity entity id (`identity_…`) — a stable, URL-safe identifier, accepted anywhere `{user}` is. The canonical profile-URL identifier when the user has no handle."},"did":{"$ref":"#/components/schemas/ProtocolDid"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's handle, or null if unset. A MUTABLE alias — a handle can change or be released; the `id` and `did` are the canonical, stable identifiers to store."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Display name, or null"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the avatar, or null"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Profile bio / description, or null"},"links":{"type":"array","items":{"$ref":"#/components/schemas/PublicProfileLinkOutput"},"description":"Ordered profile links (may be empty)"},"pinnedSpaces":{"type":"array","items":{"$ref":"#/components/schemas/PublicUserSpaceCardOutput"},"description":"The user's pinned spaces in slot order (may be empty). Terse cards — fetch the profile for the full membership shelf."}},"required":["id","did","username","displayName","avatarUrl","bio","links","pinnedSpaces"],"additionalProperties":false,"description":"A public user profile in the directory listing"},"PublicUserPageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicUserListItemOutput"},"description":"Page of public user profiles"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of public user profiles"},"PublicUserSpaceOutput":{"type":"object","properties":{"id":{"type":"string","description":"The space's entity id (`space_…`). Already public as the permanent `space-{id}` subdomain form; a stable join key for consumers that also see the space through other surfaces."},"did":{"$ref":"#/components/schemas/ProtocolDid"},"domain":{"type":"string","description":"Effective subdomain — custom domain if set, otherwise the normalized `space-{id}` form. A mutable alias; the `did` is canonical."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space display name, or null"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the space avatar, or null"},"memberCountSummary":{"type":"string","description":"Worded member-count summary (e.g. \"a few dozen members\"). Public surfaces deliberately avoid exact counts."},"pinned":{"type":"boolean","description":"Whether the user pinned this space to the top of their profile. Pinned spaces come first in the array."},"pinMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's own short note about this space, or null. Only ever set when pinned."},"pinnedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"When the user pinned this space (ISO 8601 UTC), or null when not pinned."}},"required":["id","did","domain","displayName","avatarUrl","memberCountSummary","pinned","pinMessage","pinnedAt"],"additionalProperties":false,"description":"A space on a user's public profile"},"PublicUserSpaceCardOutput":{"type":"object","properties":{"did":{"$ref":"#/components/schemas/ProtocolDid"},"domain":{"type":"string","description":"Effective subdomain — custom domain if set, otherwise the normalized `space-{id}` form. A mutable alias."},"displayName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Space display name, or null"},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolved public CDN URL for the space avatar, or null"}},"required":["did","domain","displayName","avatarUrl"],"additionalProperties":false,"description":"A terse pinned-space card on a directory listing row"},"PublicUserSpacePageOutput":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicUserSpaceOutput"},"description":"Page of a user's public spaces"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page (null if no more results forward). Pass back verbatim as `after`; do not parse."},"previousCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the previous page (null if at the beginning). Optional — may be omitted on responses that do not support backward paging. Pass back verbatim as `before`; do not parse."},"totalCount":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Total count of matching items (null if not computed). Optional — may be omitted entirely; clients must not depend on its presence."}},"required":["items","nextCursor"],"additionalProperties":false,"description":"A cursor-paginated page of a user's public spaces"},"PublicGetPostOutput":{"anyOf":[{"type":"object","properties":{"state":{"const":"eligible","description":"The post is anonymously readable — `post` carries the full public content."},"post":{"$ref":"#/components/schemas/PublicPostOutput"},"space":{"$ref":"#/components/schemas/PublicSpaceCtaOutput"}},"required":["state","post","space"],"additionalProperties":false,"description":"An anonymously-readable post with compact space context"},{"type":"object","properties":{"state":{"const":"gated","description":"The post exists in this public space but is not anonymously readable — no content is returned, only a slim space CTA."},"reason":{"enum":["signIn","spaceMembers","restricted"],"type":"string","description":"Why the post is gated. `signIn` — open to any signed-in visitor, so signing in reveals it (a sign-in wall, e.g. a `non-members` override). `spaceMembers` — plain space membership is sufficient to read, so joining the space grants access. `restricted` — a finer gate applies (a pass, a group, a private topic tier, etc.); joining the space is NOT enough on its own — visit the space to see how to gain access. Treat this as an open enum: finer reason values may be ADDED later, so handle an unrecognized value as `restricted`."},"space":{"$ref":"#/components/schemas/PublicSpaceCtaOutput"}},"required":["state","reason","space"],"additionalProperties":false,"description":"A gated post — space CTA only, no content"}],"description":"A public post, or a gated space CTA when it is not anonymously readable"},"PublicProtocolInfoOutput":{"type":"object","properties":{"relayUrl":{"type":"string","description":"Base URL of the read-only DFOS relay serving the identity and content chains for the protocol DIDs this API emits (as `did` on spaces, authors, and future entities). Combine with a `did` to resolve its DID document / identity-chain head (e.g. `{relayUrl}/proof/v1/identities/{did}`). One relay per deployment stage.","examples":["https://relay.dfos.com"]}},"required":["relayUrl"],"additionalProperties":false,"description":"DFOS protocol discovery info"}}},"paths":{"/spaces/{space}":{"get":{"operationId":"spaces.getSpace","summary":"Get a public space","description":"Fetch a space public profile + site config by its subdomain, entity id, or protocol DID. Returns 404 if the space does not exist or has no public profile (no existence leak — private and missing are indistinguishable).","tags":["Spaces"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces":{"get":{"operationId":"spaces.listSpaces","summary":"List public spaces","description":"List public, discoverable spaces, cursor-paginated. Lean discovery-card projection per item. Optionally filter by `joinMode`.","tags":["Spaces"],"parameters":[{"name":"joinMode","in":"query","schema":{"$ref":"#/components/schemas/PublicSpaceJoinMode","description":"Filter the listing to spaces with this join mode. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order."},"style":"deepObject","explode":true,"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpaceDiscoveryPageOutput"}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/posts":{"get":{"operationId":"posts.listPosts","summary":"List a space public feed","description":"List the publicly-reachable posts in a space, newest first by default, cursor-paginated. Optionally filter by format, public topic, or publication window, and select oldest-first ordering. Only posts explicitly published to the public surface are returned (a public per-post view-access override, or a post in a topic marked world-readable). Returns 404 if the space has no public profile.","tags":["Posts"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"format","in":"query","schema":{"$ref":"#/components/schemas/PublicPostFormat","description":"Filter to this post format. Open enum — only send values supported by the current contract. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["long-post"]},"style":"deepObject","explode":true,"allowEmptyValue":true,"allowReserved":true},{"name":"topic","in":"query","schema":{"type":"string","description":"Filter to a public topic ID. An unknown, non-public, or foreign topic produces an empty page. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["topic_6encc4akrze2ah9kntzd9t"]},"allowEmptyValue":true,"allowReserved":true},{"name":"sort","in":"query","schema":{"enum":["newest","oldest"],"type":"string","description":"Sort by publication time: `newest` (default) or `oldest`. Open enum — only send values supported by the current contract. `after` always means the next page in this order. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["newest"]},"allowEmptyValue":true,"allowReserved":true},{"name":"publishedAfter","in":"query","schema":{"type":"string","format":"date-time","description":"Return posts published strictly after this ISO 8601 timestamp. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["2026-07-01T00:00:00.000Z"]},"allowEmptyValue":true,"allowReserved":true},{"name":"publishedBefore","in":"query","schema":{"type":"string","format":"date-time","description":"Return posts published strictly before this ISO 8601 timestamp. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["2026-08-01T00:00:00.000Z"]},"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPostPageOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/posts/{postId}":{"get":{"operationId":"posts.getPost","summary":"Get a public post","description":"Fetch a single post in a space by ID. Returns a discriminated union on `state`: `eligible` (with the full public post content) when the post is anonymously readable, or `gated` (with a slim space CTA, no content) when the post exists in this public space but an anonymous caller cannot read it. Returns 404 if the space has no public profile, the post does not exist, or the post is reached through the wrong space.","tags":["Posts"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"postId","in":"path","required":true,"schema":{"type":"string","description":"Post ID","examples":["post_6encc4akrze2ah9kntzd9t"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicGetPostOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/pages":{"get":{"operationId":"pages.listPages","summary":"List a space public pages","description":"List a space's published pages, ordered by sort order then ID and cursor-paginated. Returns 404 if the space has no public profile. Pages are always fully public; only enabled pages appear.","tags":["Pages"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPagePageOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/pages/{page}":{"get":{"operationId":"pages.getPage","summary":"Get a public page","description":"Fetch a single published page in a space by stable page ID or slug, with its full markdown body and resolved inline media. Returns 404 if the space has no public profile, the page does not exist, belongs to another space, or is not enabled — all byte-identical (no existence leak).","tags":["Pages"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"page","in":"path","required":true,"schema":{"type":"string","description":"A page identifier: its stable entity id (`page_...`) or its mutable slug alias. Store the id; slugs may change.","examples":["page_6encc4akrze2ah9kntzd9t","about"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPageOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/events":{"get":{"operationId":"events.listSpaceEvents","summary":"List a space public events","description":"List a space's PUBLIC events within a time window, earliest first, cursor-paginated. Only events the space marked public and published appear — member-only, group-only, and admin-only events are absent, and so are drafts and cancelled events (no existence leak: an absent event is indistinguishable from one that was never created). Returns 404 if the space has no public profile. Recurring events are expanded into individual occurrences within the window; a cursor walk reaches at most the first 500 occurrences of that window (narrow the window to see past them).","tags":["Events"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time","description":"Window start (ISO 8601). Defaults to now. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["2026-08-01T00:00:00.000Z"]},"allowEmptyValue":true,"allowReserved":true},{"name":"to","in":"query","schema":{"type":"string","format":"date-time","description":"Window end (ISO 8601). Defaults to 90 days after `from`. Windows longer than ~1 year, or ending before they start, are clamped rather than rejected. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["2026-09-01T00:00:00.000Z"]},"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEventPageOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/events/upcoming":{"get":{"operationId":"events.listUpcomingEvents","summary":"List upcoming public events","description":"List upcoming PUBLIC events across every publicly-discoverable space, earliest first, cursor-paginated. Each item carries the space it belongs to. Same content rule as the per-space listing — public, published events only — with the space gate matching public space discovery (`GET /spaces`) rather than direct space lookup. Recurring events are expanded into individual occurrences within the window; a cursor walk reaches at most the first 500 occurrences of that window (narrow the window to see past them).","tags":["Events"],"parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time","description":"Window start (ISO 8601). Defaults to now. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["2026-08-01T00:00:00.000Z"]},"allowEmptyValue":true,"allowReserved":true},{"name":"to","in":"query","schema":{"type":"string","format":"date-time","description":"Window end (ISO 8601). Defaults to 90 days after `from`. Windows longer than ~1 year, or ending before they start, are clamped rather than rejected. Hold this value constant while walking pagination cursors; cursors do not encode filters or sort order.","examples":["2026-09-01T00:00:00.000Z"]},"allowEmptyValue":true,"allowReserved":true},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicEventPageOutput"}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/products":{"get":{"operationId":"products.listProducts","summary":"List a space public products","description":"List the store products a space has published public purchase pages for, ordered by product name then ID and cursor-paginated. Each item is the same object the single-product route returns. Returns 404 if the space has no public profile (byte-identical to every other not-found — no existence leak). Products the seller has not published, and archived or inactive ones, never appear; an empty list is a valid answer and reveals nothing.","tags":["Products"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProductPageOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/products/{slug}":{"get":{"operationId":"products.getProduct","summary":"Get a public product","description":"Fetch a store product's public purchase page by its slug. Returns 404 if the space has no public profile, the slug matches no product, the product belongs to another space, the seller has not published a public page for it, or the product is archived or inactive — all byte-identical (no existence leak).","tags":["Products"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"The product's public purchase-page slug (the `/p/{slug}` segment on the space site). Mutable — changing it breaks old links with no redirect; store the `id`.","examples":["softr-ticket"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProductOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/spaces/{space}/topics":{"get":{"operationId":"topics.listTopics","summary":"List a space's public topics","description":"List the live, world-readable topics in a space, ordered by name then ID, cursor-paginated. Returns 404 if the space has no public profile.","tags":["Topics"],"parameters":[{"name":"space","in":"path","required":true,"schema":{"type":"string","description":"A space identifier: its subdomain (custom domain if set, else the normalized `space-{id}` form), its entity id (`space_…`), or its protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The subdomain is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["metalabel","space_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTopicPageOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/users/{user}":{"get":{"operationId":"users.getUser","summary":"Get a public user profile","description":"Fetch a public user profile by its handle (username), its identity entity id, or its protocol DID. Handles are mutable aliases — the id and DID are canonical. Returns 404 if the user does not exist, has opted their profile out of public visibility, or is not a user identity (no existence leak — private and missing are indistinguishable).","tags":["Users"],"parameters":[{"name":"user","in":"path","required":true,"schema":{"type":"string","description":"A user identifier: their handle (username), their identity entity id (`identity_…`), or their protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The handle is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["bvalosek","identity_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicUserOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/users":{"get":{"operationId":"users.listUsers","summary":"List public user profiles","description":"List public member profiles, cursor-paginated. Profiles are public by default and owners can opt out — an opted-out profile does not appear here. Each row carries only the terse pinned-space cards; fetch the profile for the full membership shelf. Ordering is deterministic and cursor-stable but otherwise unspecified; do not depend on it.","tags":["Users"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicUserPageOutput"}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/users/{user}/spaces":{"get":{"operationId":"users.getUserSpaces","summary":"List a user's public spaces","description":"The complete list of public spaces a user belongs to, cursor-paginated: pinned spaces first in the user's chosen order, then the rest by join recency. A space appears only if it is public, the space allows it, and the user has not hidden it — so this is a curated shelf, not a membership audit. Never includes roles, member numbers, or join dates. Returns 404 if the user does not exist, has opted their profile out of public visibility, or is not a user identity (no existence leak). A public profile with nothing to show returns an empty list, not a 404.","tags":["Users"],"parameters":[{"name":"user","in":"path","required":true,"schema":{"type":"string","description":"A user identifier: their handle (username), their identity entity id (`identity_…`), or their protocol DID (`did:dfos:` + 31 chars, as emitted in `did`). The handle is a mutable alias; the id and DID are canonical. Legacy internal identifiers are rejected.","examples":["bvalosek","identity_6encc4akrze2ah9kntzd9t","did:dfos:6encc4akrze2ah9kntzd9tc8zr24crc"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of items per page (default: 20, max: 100)","examples":[20]},"allowEmptyValue":true,"allowReserved":true},{"name":"after","in":"query","schema":{"type":"string","description":"Opaque cursor for forward pagination — fetch items after this cursor. Treat as an opaque token: pass back a `nextCursor`/`previousCursor` verbatim; do not parse or construct it (its encoding carries no stability guarantee).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true},{"name":"before","in":"query","schema":{"type":"string","description":"Opaque cursor for backward pagination — fetch items before this cursor. Treat as an opaque token (see `after`).","examples":["eyJpZCI6InBvc3RfNmVuY2M0YWtyemUyYWg5a250emQ5dCJ9"]},"allowEmptyValue":true,"allowReserved":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicUserSpacePageOutput"}}}},"404":{"description":"404","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_NOT_FOUND"},"status":{"const":404},"message":{"type":"string","default":"Not found. A missing space and a non-public (private) one return a byte-identical 404 by design — the two are deliberately indistinguishable (no existence leak)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}},"/protocol":{"get":{"operationId":"protocol.getProtocolInfo","summary":"Get protocol discovery info","description":"Return protocol-layer discovery info for this API deployment — today, the relay endpoint that serves the identity and content chains for the protocol DIDs this API emits. The response is an object so more protocol metadata can be added without a wire break.","tags":["Protocol"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProtocolInfoOutput"}}}},"429":{"description":"429","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_RATE_LIMITED"},"status":{"const":429},"message":{"type":"string","default":"Rate limit exceeded — retry after `retryAfterMs`."},"data":{"type":"object","properties":{"scope":{"type":"string","description":"Which per-IP budget was exhausted"},"retryAfterMs":{"type":"number","description":"Milliseconds to wait before retrying"}},"required":["scope","retryAfterMs"]}},"required":["defined","code","status","message","data"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}},"503":{"description":"503","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"defined":{"const":true},"code":{"const":"E_SERVICE_UNAVAILABLE"},"status":{"const":503},"message":{"type":"string","default":"Service temporarily unavailable — the rate-limit store was unreachable (fail-closed)."},"data":{}},"required":["defined","code","status","message"]},{"type":"object","properties":{"defined":{"const":false},"code":{"type":"string"},"status":{"type":"number"},"message":{"type":"string"},"data":{}},"required":["defined","code","status","message"]}]}}}}}}}}}