info:
  title: The DFOS API
  version: 1.0.0-beta
  description: >-
    The external HTTP API for Dark Forest OS — space profiles, public feeds, and

    public posts.


    Every endpoint today is an anonymous `GET`: no credentials are required or

    accepted, and every response is viewer-independent. Data responses are

    `no-store` (only the generated spec is CDN-cached), so referenced media in a

    public post comes back as time-limited SIGNED URLs inline — never persist
    those,

    re-fetch the post for fresh ones (see `urlExpiresAt`). A non-public or
    missing

    space returns a 404 that is deliberately indistinguishable from any other —
    the

    API never reveals whether a space it won't serve actually exists.


    **Forward-compatibility — clients MUST:**

    - **Tolerate unknown fields.** New response fields are added without a
    version
      bump; ignore fields you don't recognize.
    - **Tolerate unknown enum values.** Enums (e.g. `joinMode`, `siteMode`) can
    grow
      new members; treat an unrecognized value as an opaque string, not an error.
    - **Treat cursors as opaque tokens.** Pass `nextCursor`/`previousCursor`
    back
      verbatim; never parse or construct a cursor.
    - **Treat subdomains as mutable aliases.** A space's subdomain can change or
    be
      reassigned; the space `id` and protocol `did` are the canonical, stable
      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
