openapi: 3.1.0
info:
  title: Substack API
  version: 1.0.0
  summary: Reverse-engineered HTTP API surface used by the substack-api TypeScript client
  description: |
    This document describes the **underlying HTTP API** that Substack exposes to its
    web and mobile clients. The [`substack-api`](https://github.com/christopher-s/substack-api)
    TypeScript library wraps these endpoints and is the source of truth for this
    specification — every operation here corresponds to a method in
    [`src/internal/services/`](https://github.com/christopher-s/substack-api/tree/main/src/internal/services).

    ## Hosts

    Substack uses two hostname classes:

    - **`https://substack.com`** for global endpoints (search, top posts, profiles by ID, …).
    - **`https://{publication}.substack.com`** for publication-scoped endpoints
      (a single newsletter's archive, comments, homepage, notes, …). The
      `publication` server variable is the subdomain segment of the publication
      (e.g. `stratechery`, `astralcodexten`).

    Choose the matching server when invoking each operation; operations document
    which class of host they belong to in their `tags` and `summary`.

    ## Authentication

    Authenticated requests use a session cookie named `substack.sid`. The cookie
    is established by the standard Substack web sign-in flow and copied into the
    client (see [README → Authentication](https://github.com/christopher-s/substack-api#authentication)).
    Operations that require a logged-in user declare
    `security: [{ substackSession: [] }]`.

    ## Rate limiting

    The TypeScript client applies a configurable client-side rate limit
    (`maxRequestsPerSecond`, default 25 RPS). The Substack server itself does
    not document rate limits, but expect HTTP 429 from sustained burst traffic.

    ## Stability

    This is a **reverse-engineered** specification. Substack does not publish a
    public API contract, so every field, route, and behaviour is empirical and
    may change without notice. Pin the client library version to a tested
    release for stability.

    ## Naming conventions

    Path segments mirror the underlying Substack API, which mixes
    underscore-separated (`post_management`, `live_streams`) and
    dash-separated (`post-tag`, `by-id`) naming. This spec preserves
    the original naming for accuracy.
  contact:
    name: substack-api maintainers
    url: https://github.com/christopher-s/substack-api/issues
  license:
    name: MIT
    url: https://github.com/christopher-s/substack-api/blob/main/LICENSE
externalDocs:
  description: substack-api source repository
  url: https://github.com/christopher-s/substack-api
servers:
  - url: https://substack.com
    description: Global Substack host (search, profiles by ID, top posts, categories, etc.)
  - url: https://{publication}.substack.com
    description: Publication-scoped host (archive, homepage, comments on a post, notes feed, …)
    variables:
      publication:
        default: 'on'
        description: |
          Publication subdomain (the segment before `.substack.com`). For example,
          `stratechery` for `https://stratechery.substack.com`. The default
          `on` points at Substack's official "On Substack" newsletter.
tags:
  - name: post
    description: Posts (newsletter articles).
  - name: profile
    description: User profiles, handles, and authenticated user details.
  - name: note
    description: Notes (short-form posts) and the comment-feed surface that backs them.
  - name: comment
    description: Threaded comments on posts, including replies.
  - name: discovery
    description: Discovery surfaces — top posts, trending, search, categories, explore feeds.
  - name: publication
    description: Publication-scoped reads (archive, homepage, posts, facepile, livestreams).
  - name: following
    description: Authenticated user's following / subscriber-list operations.
  - name: connectivity
    description: Connectivity / authentication probes.
  - name: post-management
    description: Draft CRUD, post management counts, and publishing workflows.
  - name: subscription
    description: Subscription status (per-publication and global).
  - name: settings
    description: Publisher and publication settings.
  - name: dashboard
    description: Publisher dashboard, stats, activity, and growth suggestions.
  - name: recommendations
    description: Publication recommendations and recommendation stats.
security: []
paths:
  /api/v1/posts/by-id/{id}:
    get:
      tags:
        - post
      summary: Get a post by numeric ID
      description: Fetch full post details (body, byline, reactions, paywall metadata) by post ID.
      operationId: getPostById
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric post ID.
      responses:
        '200':
          description: Full post payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FullPost'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/profile/posts:
    get:
      tags:
        - post
        - profile
      summary: List preview posts for a profile
      description: Paginated preview posts authored by a given user (offset/limit pagination).
      operationId: getPostsForProfile
      parameters:
        - in: query
          name: profile_user_id
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric user ID whose posts to list.
        - in: query
          name: limit
          required: true
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of results to return per page.
        - in: query
          name: offset
          required: true
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of results to skip for pagination.
      responses:
        '200':
          description: Array of preview posts.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PreviewPost'
  /api/v1/handle/options:
    get:
      tags:
        - profile
      summary: List handle options for the authenticated user
      description: |
        Returns potential handles associated with the signed-in account. Used
        internally to discover the user's own handle/slug.
      operationId: getOwnSlug
      security:
        - substackSession: []
      responses:
        '200':
          description: Potential handle records.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PotentialHandles'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/user/{slug}/public_profile:
    get:
      tags:
        - profile
      summary: Get a public profile by slug
      description: |
        Resolve a user's handle/slug to their full public profile. Used both
        for arbitrary lookups and (when authenticated and resolving the
        signed-in user's own slug) to fetch the authenticated profile.
      operationId: getProfileBySlug
      parameters:
        - in: path
          name: slug
          required: true
          schema:
            type: string
            minLength: 1
          description: User handle / slug (the segment after `/@` in profile URLs).
      responses:
        '200':
          description: Full public profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FullProfile'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/reader/feed/profile/{profileId}:
    get:
      tags:
        - profile
        - discovery
        - note
      summary: Profile reader feed
      description: |
        Multi-purpose endpoint that serves a profile's reader feed. The shape
        of the response varies by the `types` / `types[]` / `tab` query
        parameter:

        - `types=note` — a single note feed (comments-with-attachments).
        - `tab=posts|notes|comments|likes` — a profile activity feed (also
          used for likes via `tab=likes` *or* `types[]=like`, depending on
          the call site).
        - no parameters — resolves the user ID to a slug-bearing profile
          record (see `getProfileById`).

        Operations `getNotesForProfile`, `getProfileActivity`, and
        `getProfileLikes` all hit this same path with different query strings.
      operationId: getReaderProfileFeed
      parameters:
        - in: path
          name: profileId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric profile (user) ID.
        - in: query
          name: types
          schema:
            type: string
          description: Comma-separated content types (e.g. `note` for notes-only feed).
        - in: query
          name: types[]
          schema:
            type: string
          description: Repeated parameter for content type filtering (e.g. `types[]=like`).
        - in: query
          name: tab
          schema:
            type: string
            enum:
              - posts
              - notes
              - comments
              - likes
          description: Activity tab when used as a profile activity feed.
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Either a `UserProfile` (no params) or a paginated reader feed.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/UserProfile'
                  - $ref: '#/components/schemas/PaginatedFeed'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/reader/comment/{id}:
    get:
      tags:
        - note
        - comment
      summary: Get a comment / note by ID
      description: |
        Fetch a single comment by ID. Substack treats notes as comments under
        the hood — this endpoint serves both. Used by `noteForId` and
        `commentForId`.
      operationId: getCommentById
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric comment ID.
      responses:
        '200':
          description: Comment response wrapper.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommentResponse'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/reader/comment/{commentId}/replies:
    get:
      tags:
        - comment
      summary: Threaded replies to a comment
      description: Cursor-paginated replies under a parent comment.
      operationId: getCommentReplies
      parameters:
        - in: path
          name: commentId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric comment ID to get replies for.
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Paginated reply branches.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommentRepliesResponse'
  /api/v1/notes:
    get:
      tags:
        - note
      summary: List notes for the authenticated user
      description: Cursor-paginated notes feed for the signed-in user.
      operationId: getNotesForLoggedUser
      security:
        - substackSession: []
      parameters:
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Paginated notes payload (raw shape).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedFeed'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/note_stats/{entityKey}:
    get:
      tags:
        - note
      summary: Analytics stats for a note
      description: |
        Card-based analytics for a single note. Returns an array of "cards"
        covering impressions (line chart over time), surfaces (bar chart of
        impression sources like feed, profile, email), audience (bar chart of
        viewer types like free, paid, other), and interactions (list of
        engagement actions — likes, restacks, replies).
      operationId: getNoteStats
      security:
        - substackSession: []
      parameters:
        - in: path
          name: entityKey
          required: true
          schema:
            type: string
          description: |
            Note entity key, prefixed with `c-` (e.g. `c-251155220`).
            Derived from the comment ID used in the `/reader/comment/{id}` endpoint.
      responses:
        '200':
          description: Note analytics cards.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NoteStats'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Note not found or no stats available.
  /api/v1/post/{postId}/comments:
    get:
      tags:
        - comment
      summary: Comments on a post
      description: Top-level comments for a post, with a `more` flag for additional pages.
      operationId: getCommentsForPost
      parameters:
        - $ref: '#/components/parameters/PostIdPath'
      responses:
        '200':
          description: Comments and pagination flag.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedComments'
  /api/v1/inbox/top:
    get:
      tags:
        - discovery
      summary: Top / trending inbox posts
      description: |
        Substack's "top" inbox feed. Used for both `topPosts` (no params) and
        `trending` (with `limit`/`offset`) — the two are aliases of the same
        upstream endpoint.
      operationId: getTopInboxFeed
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
          description: Maximum number of results to return per page.
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
          description: Number of results to skip for pagination.
      responses:
        '200':
          description: Inbox feed payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TopInboxFeed'
  /api/v1/reader/feed:
    get:
      tags:
        - discovery
      summary: Discovery reader feed
      description: |
        The main discovery feed exposed to readers. `tab` selects the surface
        (`for-you`, `top`, `popular`, `catchup`, `notes`, `explore`); `type`
        is a required content-type filter.
      operationId: getReaderFeed
      parameters:
        - in: query
          name: tab
          schema:
            type: string
            enum:
              - for-you
              - top
              - popular
              - catchup
              - notes
              - explore
          description: Feed tab filter (e.g. `for_you`, `following`).
        - in: query
          name: type
          required: true
          schema:
            type: string
          description: Required content-type filter (e.g. `posts`, `notes`).
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Paginated reader feed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedFeed'
  /api/v1/categories:
    get:
      tags:
        - discovery
      summary: List all categories with subcategories
      operationId: getCategories
      responses:
        '200':
          description: All known categories.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Category'
  /api/v1/category/public/{categoryId}/posts:
    get:
      tags:
        - discovery
        - publication
      summary: Publications in a category
      description: Paginated publications for a given category id or slug.
      operationId: getCategoryPublications
      parameters:
        - in: path
          name: categoryId
          required: true
          schema:
            oneOf:
              - type: integer
                minimum: 1
              - type: string
                minLength: 1
          description: Category numeric ID or slug (`tech`, `podcast`, …).
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
          description: Maximum number of results to return per page.
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
          description: Number of results to skip for pagination.
      responses:
        '200':
          description: Publications page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryPublications'
  /api/v1/top/search:
    get:
      tags:
        - discovery
      summary: Top search across all surfaces
      description: Cursor-paginated search across posts, people, publications, and notes.
      operationId: searchAll
      parameters:
        - in: query
          name: query
          required: true
          schema:
            type: string
            minLength: 1
          description: Search query string.
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Paginated mixed-content search results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedFeed'
  /api/v1/profile/search:
    get:
      tags:
        - discovery
        - profile
      summary: Profile search
      description: Page-paginated search across user profiles.
      operationId: searchProfiles
      parameters:
        - in: query
          name: query
          required: true
          schema:
            type: string
            minLength: 1
          description: Search query string.
        - in: query
          name: page
          schema:
            type: integer
            minimum: 1
          description: Page number for pagination.
      responses:
        '200':
          description: Profile search results page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProfileSearchResponse'
  /api/v1/search/explore/web:
    get:
      tags:
        - discovery
      summary: Explore search across tabs
      operationId: exploreSearch
      parameters:
        - in: query
          name: tab
          schema:
            type: string
          description: Feed tab filter (e.g. `for_you`, `following`).
        - in: query
          name: type
          required: true
          schema:
            type: string
          description: Content type filter.
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Paginated explore results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedFeed'
  /api/v1/reader/feed/publication/{publicationId}:
    get:
      tags:
        - discovery
        - publication
      summary: Publication activity feed
      description: Activity feed for a publication (posts, notes).
      operationId: getPublicationFeed
      parameters:
        - in: path
          name: publicationId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric publication ID.
        - in: query
          name: tab
          schema:
            type: string
          description: Feed tab filter (e.g. `for_you`, `following`).
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Paginated publication activity feed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedFeed'
  /api/v1/homepage_data:
    get:
      tags:
        - publication
      summary: Publication homepage data
      description: |
        Returns a publication's homepage payload — new posts, pinned posts,
        sections, and metadata. Must be called against the publication-scoped
        host (`https://{publication}.substack.com`).
      operationId: getHomepageData
      responses:
        '200':
          description: Homepage payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HomepageData'
  /api/v1/archive:
    get:
      tags:
        - publication
      summary: Publication archive
      description: Paginated post archive for a publication, with sort and search filters.
      operationId: getArchive
      parameters:
        - in: query
          name: sort
          schema:
            type: string
            enum:
              - new
              - top
          description: Sort order for results (e.g. `new`, `top`).
        - in: query
          name: search
          schema:
            type: string
          description: Full-text search filter for post titles and bodies.
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
          description: Number of results to skip for pagination.
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
          description: Maximum number of results to return per page.
      responses:
        '200':
          description: Paginated publication posts.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PublicationPost'
  /api/v1/posts:
    get:
      tags:
        - publication
      summary: Publication full posts
      description: |
        Full posts (including `body_html`) for a publication, paginated by
        offset/limit. Must be called against the publication-scoped host.
      operationId: getPublicationPosts
      parameters:
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
          description: Number of results to skip for pagination.
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
          description: Maximum number of results to return per page.
      responses:
        '200':
          description: Paginated full publication posts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedPosts'
  /api/v1/post/{postId}/facepile:
    get:
      tags:
        - publication
      summary: Reactor facepile for a post
      description: List of users who reacted to a post.
      operationId: getPostFacepile
      parameters:
        - $ref: '#/components/parameters/PostIdPath'
      responses:
        '200':
          description: Facepile payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Facepile'
  /api/v1/live_streams/active/pub/{publicationId}:
    get:
      tags:
        - publication
      summary: Active live stream for a publication
      operationId: getActiveLiveStream
      parameters:
        - in: path
          name: publicationId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric publication ID.
      responses:
        '200':
          description: Live-stream payload (or empty if none active).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LiveStreamResponse'
  /api/v1/posts/{postId}/seen:
    post:
      tags:
        - publication
      summary: Mark a post as seen
      description: Records that the authenticated reader has seen a post.
      operationId: markPostSeen
      security:
        - substackSession: []
      parameters:
        - $ref: '#/components/parameters/PostIdPath'
      responses:
        '200':
          description: Seen state acknowledged.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '204':
          description: Seen state acknowledged (no body).
  /api/v1/user/{userId}/subscriber-lists:
    get:
      tags:
        - following
      summary: Authenticated user's following / subscriber lists
      operationId: getSubscriberLists
      security:
        - substackSession: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Numeric user ID.
        - in: query
          name: lists
          required: true
          schema:
            type: string
          description: Comma-separated list keys (the client passes `following`).
      responses:
        '200':
          description: Subscriber-list payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriberLists'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/comment/feed/:
    post:
      tags:
        - note
      summary: Publish a note
      description: |
        Publishes a new note. The body is a ProseMirror document. When the
        client publishes a note with a link attachment it first calls
        `POST /api/v1/comment/attachment/` to obtain an attachment id, then
        passes that id in this body.
      operationId: publishNote
      security:
        - substackSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublishNoteRequest'
      responses:
        '200':
          description: Published note.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishNoteResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/comment/attachment/:
    post:
      tags:
        - note
      summary: Create a link attachment for a note
      description: |
        Creates a link-preview attachment that can be referenced by a
        subsequent `POST /api/v1/comment/feed/`. Step 1 of the
        "note with link" two-step publish.
      operationId: createNoteAttachment
      security:
        - substackSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAttachmentRequest'
      responses:
        '200':
          description: Attachment record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateAttachmentResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/post_management/published:
    get:
      tags:
        - post-management
      summary: List published posts (publisher dashboard)
      description: |
        Paginated list of published posts for the publication. Requires
        authentication and a publication-scoped host.
      operationId: getPublishedPosts
      security:
        - substackSession: []
      parameters:
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of results to skip for pagination.
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of results to return per page.
        - in: query
          name: order_by
          schema:
            type: string
            default: post_date
          description: Sort field (e.g. `post_date`).
        - in: query
          name: order_direction
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          description: Sort direction (`asc` or `desc`).
      responses:
        '200':
          description: Paginated list of published posts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostManagementResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/post_management/drafts:
    get:
      tags:
        - post-management
      summary: List drafts (publisher dashboard)
      description: |
        Paginated list of draft posts for the publication. Requires
        authentication and a publication-scoped host.
      operationId: getDrafts
      security:
        - substackSession: []
      parameters:
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of results to skip for pagination.
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of results to return per page.
        - in: query
          name: order_by
          schema:
            type: string
            default: draft_updated_at
          description: Field to sort results by (e.g. `post_date`).
        - in: query
          name: order_direction
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          description: Sort direction (`asc` or `desc`).
      responses:
        '200':
          description: Paginated list of drafts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostManagementResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/post_management/scheduled:
    get:
      tags:
        - post-management
      summary: List scheduled posts (publisher dashboard)
      description: |
        Paginated list of scheduled posts. Requires authentication and a
        publication-scoped host.
      operationId: getScheduledPosts
      security:
        - substackSession: []
      parameters:
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of results to skip for pagination.
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of results to return per page.
        - in: query
          name: order_by
          schema:
            type: string
            default: trigger_at
          description: Field to sort results by (e.g. `post_date`).
        - in: query
          name: order_direction
          schema:
            type: string
            enum:
              - asc
              - desc
            default: asc
          description: Sort direction (`asc` or `desc`).
      responses:
        '200':
          description: Paginated list of scheduled posts.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostManagementResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/post_management/counts:
    get:
      tags:
        - post-management
      summary: Get post counts by status
      description: |
        Returns counts of posts grouped by status (published, draft, scheduled,
        etc.) for the publication. Requires authentication and a
        publication-scoped host.
      operationId: getPostCounts
      security:
        - substackSession: []
      parameters:
        - in: query
          name: query
          schema:
            type: string
            default: ''
          description: Optional search filter for counting.
      responses:
        '200':
          description: Post count summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostManagementCounts'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/drafts/{id}:
    get:
      tags:
        - post-management
      summary: Get a single draft by ID
      description: Fetches a draft post by its numeric ID. Requires authentication.
      operationId: getDraft
      security:
        - substackSession: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftPost'
          description: Returns the full draft object.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      tags:
        - post-management
      summary: Update a draft
      description: |
        Updates a draft's title, body, or other fields. Requires authentication
        and a publication-scoped host.
      operationId: updateDraft
      security:
        - substackSession: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                draft_title:
                  type: string
                draft_body:
                  type: string
                  description: Updated HTML body.
              additionalProperties: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftPost'
          description: Returns the updated draft.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags:
        - post-management
      summary: Delete a draft
      description: Permanently deletes a draft post. Requires authentication.
      operationId: deleteDraft
      security:
        - substackSession: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Deletion confirmed.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/drafts:
    post:
      tags:
        - post-management
      summary: Create a new draft
      description: |
        Creates a new draft post for the publication. Requires authentication
        and a publication-scoped host.
      operationId: createDraft
      security:
        - substackSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - draft_title
              properties:
                draft_title:
                  type: string
                draft_body:
                  type: string
                  description: HTML body content.
                type:
                  type: string
                  default: newsletter
                  description: Post type (e.g. `newsletter`).
                audience:
                  type: string
                  default: everyone
                  description: Audience visibility.
                draft_bylines:
                  type: array
                  items:
                    type: object
                  description: Byline entries with user IDs.
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftPost'
          description: Returns the newly created draft.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/drafts/{id}/publish:
    post:
      tags:
        - post-management
      summary: Publish a draft
      description: |
        Publishes an existing draft immediately. Requires authentication and a
        publication-scoped host.
      operationId: publishDraft
      security:
        - substackSession: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
          description: Draft ID to publish.
      responses:
        '200':
          description: Published post.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftPost'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/publication:
    get:
      tags:
        - publication
      summary: Get publication details
      description: |
        Returns metadata about the publication (name, subdomain, description,
        etc.). Works on the publication-scoped host.
      operationId: getPublicationDetails
      responses:
        '200':
          description: Publication metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicationDetail'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/publication/post-tag:
    get:
      tags:
        - publication
      summary: Get publication post tags
      description: Returns the set of tags configured for the publication. Works on the publication-scoped host.
      operationId: getPostTags
      responses:
        '200':
          description: Array of tag objects.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PostTag'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/subscription:
    get:
      tags:
        - subscription
      summary: Get current subscription to a publication
      description: |
        Returns the authenticated user's subscription status for the
        publication-scoped host. Requires authentication.
      operationId: getCurrentSubscription
      security:
        - substackSession: []
      responses:
        '200':
          description: Subscription record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/subscriptions/page_v2:
    get:
      tags:
        - subscription
      summary: List all subscriptions (global)
      description: |
        Paginated list of all subscriptions for the authenticated user across
        all publications. Uses the global `substack.com` host. Requires
        authentication but no publication scope.
      operationId: getAllSubscriptions
      security:
        - substackSession: []
      parameters:
        - in: query
          name: offset
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of results to skip for pagination.
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Maximum number of results to return per page.
      responses:
        '200':
          description: Paginated subscription list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/settings:
    get:
      tags:
        - settings
      summary: Get publisher settings
      description: |
        Returns the publication's publisher settings (publication name, author
        details, etc.). Requires authentication and a publication-scoped host.
      operationId: getPublisherSettings
      security:
        - substackSession: []
      responses:
        '200':
          description: Publisher settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublisherSettings'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/post/{postId}/comment:
    post:
      tags:
        - comment
      summary: Create a comment on a post
      description: |
        Posts a new comment on the specified post. Requires authentication and
        a publication-scoped host.
      operationId: createComment
      security:
        - substackSession: []
      parameters:
        - in: path
          name: postId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Post ID to comment on.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - body
                - post_id
              properties:
                body:
                  type: string
                  description: Comment body text.
                post_id:
                  type: integer
                  description: Post ID (repeated in body).
      responses:
        '200':
          description: Created comment.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/comment/{commentId}:
    delete:
      tags:
        - comment
      summary: Delete a comment
      description: |
        Permanently deletes a comment. Requires authentication and a
        publication-scoped host.
      operationId: deleteComment
      security:
        - substackSession: []
      parameters:
        - in: path
          name: commentId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Comment ID to delete.
      responses:
        '200':
          description: Deletion confirmed.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/live_streams:
    get:
      tags:
        - publication
      summary: List live streams
      description: |
        Returns live streams for the publication, optionally filtered by
        status. Uses the publication-scoped host.
      operationId: getLiveStreams
      parameters:
        - in: query
          name: status
          schema:
            type: string
            default: scheduled
          description: Filter by stream status (e.g. `scheduled`, `active`, `ended`).
      responses:
        '200':
          description: Live stream list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LiveStreamList'
  /api/v1/live_stream/eligible_hosts:
    get:
      tags:
        - publication
      summary: Get eligible live stream hosts
      description: |
        Returns users eligible to host a live stream for the given publication.
        Uses the publication-scoped host.
      operationId: getEligibleHosts
      parameters:
        - in: query
          name: publication_id
          required: true
          schema:
            type: integer
            minimum: 1
          description: Publication ID.
      responses:
        '200':
          description: Array of eligible host user objects.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
  /api/v1/publish-dashboard/summary-v2:
    get:
      tags:
        - dashboard
      summary: Publish dashboard summary (v2)
      description: |
        Returns an extended dashboard summary with additional metrics compared
        to v1. Requires authentication and a publication-scoped host.
      operationId: getDashboardSummaryV2
      security:
        - substackSession: []
      responses:
        '200':
          description: Extended dashboard summary metrics.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DashboardSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
  # ── Publication Stats: Network ──────────────────────────────────

  /api/v1/publication/stats/network_attribution:
    get:
      tags:
        - stats
      summary: Network attribution for subscriber acquisition
      description: |
        Shows where subscribers come from within the Substack network. Each row
        represents a source (e.g. search, recommendations, cross-promotion) with
        subscriber counts and percentage share for the given time window.
      operationId: getNetworkAttribution
      security:
        - substackSession: []
      parameters:
        - in: query
          name: time_window
          schema:
            type: string
            default: 90+days
          description: Time window for attribution data.
        - in: query
          name: is_subscribed
          schema:
            type: boolean
            default: false
          description: Filter to subscribed users only.
      responses:
        '200':
          description: Network attribution rows.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  rows:
                    type: array
                    items:
                      type: object
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Publication Stats: Audience ─────────────────────────────────

  /api/v1/publication/stats/followers/timeseries:
    get:
      tags:
        - stats
      summary: Follower count time series
      description: |
        Returns follower count chart data over time. Used in the Audience tab
        to display how the publication's follower base has grown.
      operationId: getFollowerTimeseries
      security:
        - substackSession: []
      parameters:
        - in: query
          name: from
          required: true
          schema:
            type: string
            format: date
          description: Start date for the time range (ISO format).
      responses:
        '200':
          description: Follower count time series data.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  timeseries:
                    type: array
                    items:
                      type: array
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/audience_insights/location:
    get:
      tags:
        - stats
      summary: Audience geographic breakdown
      description: |
        Returns a geographic breakdown of the audience. The granularity
        parameter controls whether results are US-state-level or global-country-level.
        Used in the Audience tab to render the location map.
      operationId: getAudienceLocation
      security:
        - substackSession: []
      parameters:
        - in: query
          name: metric
          schema:
            type: string
            default: free+signups
          description: Audience metric to measure (e.g. free+signups, paid+signups).
        - in: query
          name: granularity
          schema:
            type: string
            default: usa
          description: Geographic granularity — usa for US states, global for countries.
      responses:
        '200':
          description: Audience location data.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/audience_insights/location/total:
    get:
      tags:
        - stats
      summary: Aggregate audience location totals
      description: |
        Returns aggregate location totals across all granularities. Used in the
        Audience tab to display summary counts for global and US audiences.
      operationId: getAudienceLocationTotal
      security:
        - substackSession: []
      responses:
        '200':
          description: Aggregate location totals.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  global:
                    type: object
                    properties:
                      locations:
                        type: integer
                      total:
                        type: integer
                  usa:
                    type: object
                    properties:
                      locations:
                        type: integer
                      total:
                        type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/audience_insights/overlap:
    get:
      tags:
        - stats
      summary: Publications with overlapping audiences
      description: |
        Returns other publications whose audiences overlap with this one. Each
        entry includes the overlap percentage and the publication metadata.
        Used in the Audience tab to show audience similarity.
      operationId: getAudienceOverlap
      security:
        - substackSession: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 6
          description: Maximum number of overlapping publications to return.
      responses:
        '200':
          description: Audience overlap data.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  additionalProperties: true
                  properties:
                    percentOverlap:
                      type: string
                    pub:
                      type: object
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Publication Stats: Traffic ───────────────────────────────────

  /api/v1/publication/stats/publication_traffic/30d_views:
    get:
      tags:
        - stats
      summary: 30-day view count with delta
      description: |
        Returns the total view count for the past 30 days along with the
        percentage change from the prior period. Displayed as a KPI card in
        the Traffic tab of the publisher dashboard.
      operationId: getTraffic30dViews
      security:
        - substackSession: []
      responses:
        '200':
          description: 30-day traffic summary.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  views:
                    type: integer
                  viewsDiff:
                    type: number
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/visitor_sources:
    get:
      tags:
        - stats
      summary: Breakdown of visitor sources
      description: |
        Returns a paginated list of where visitors come from (e.g. direct,
        search, social). Each row has the source name, view count, and
        visitor count. Used in the Traffic tab source breakdown table.
      operationId: getVisitorSources
      security:
        - substackSession: []
      parameters:
        - in: query
          name: from_date
          required: true
          schema:
            type: string
            format: date
          description: Start date for the time range.
        - in: query
          name: to_date
          required: true
          schema:
            type: string
            format: date
          description: End date for the time range.
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
          description: Pagination offset.
        - in: query
          name: limit
          schema:
            type: integer
            default: 20
          description: Maximum number of results to return.
        - in: query
          name: order_by
          schema:
            type: string
            default: views
          description: Field to sort results by.
        - in: query
          name: order_direction
          schema:
            type: string
            default: desc
          description: Sort direction (asc or desc).
      responses:
        '200':
          description: Visitor source breakdown.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/publication_traffic/timeseries:
    get:
      tags:
        - stats
      summary: Traffic time series
      description: |
        Returns traffic chart data over time as an array of [date, value] pairs.
        Optionally filtered by traffic category (e.g. organic). Used in the
        Traffic tab to render the views chart.
      operationId: getTrafficTimeseries
      security:
        - substackSession: []
      parameters:
        - in: query
          name: from
          required: true
          schema:
            type: string
            format: date
          description: Start date for the time range.
        - in: query
          name: to
          required: true
          schema:
            type: string
            format: date
          description: End date for the time range.
        - in: query
          name: category
          schema:
            type: string
          description: Optional traffic category filter (e.g. organic).
      responses:
        '200':
          description: Traffic time series data.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: array
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Publication Stats: Posts / Email ─────────────────────────────

  /api/v1/publication/stats/email_stats/30d_open_rate:
    get:
      tags:
        - stats
      summary: 30-day email open rate KPI
      description: |
        Returns the aggregate email open rate for the past 30 days along with
        the change from the prior period. Displayed as a KPI card in the Posts
        tab of the publisher dashboard.
      operationId: getEmail30dOpenRate
      security:
        - substackSession: []
      responses:
        '200':
          description: 30-day email open rate.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  openRate:
                    type: number
                  openRateDiff:
                    type: number
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/email_stats:
    get:
      tags:
        - stats
      summary: Per-post email statistics
      description: |
        Returns a paginated table of per-post email statistics including open
        rates, click rates, and delivery counts. Each row represents a single
        post's email campaign performance.
      operationId: getEmailStats
      security:
        - substackSession: []
      parameters:
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
          description: Pagination offset.
        - in: query
          name: limit
          schema:
            type: integer
            default: 20
          description: Maximum number of results to return.
        - in: query
          name: order_by
          schema:
            type: string
            default: post_date
          description: Field to sort results by.
        - in: query
          name: order_direction
          schema:
            type: string
            default: desc
          description: Sort direction (asc or desc).
      responses:
        '200':
          description: Per-post email statistics.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  rows:
                    type: array
                    items:
                      type: object
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Publication Stats: Pledges ───────────────────────────────────

  /api/v1/publication/stats/payment_pledges/summary:
    get:
      tags:
        - stats
      summary: Aggregate pledge revenue summary
      description: |
        Returns aggregate pledge revenue totals including total pledge count and
        total monetary amount. Displayed as the headline KPI in the Pledges tab
        of the publisher dashboard.
      operationId: getPledgeSummary
      security:
        - substackSession: []
      responses:
        '200':
          description: Pledge revenue summary.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  totalPledges:
                    type: integer
                  totalPledgeAmount:
                    type: integer
                  currency:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/payment_pledges:
    get:
      tags:
        - stats
      summary: Individual pledge records
      description: |
        Returns a list of individual pledge records with user details and pledge
        metadata (amount, interval, founding status, optional note). Used in the
        Pledges tab to display the pledge ledger.
      operationId: getPledges
      security:
        - substackSession: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 20
          description: Maximum number of pledge records to return.
      responses:
        '200':
          description: Individual pledge records.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  pledgeAndUserData:
                    type: array
                    items:
                      type: object
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Publication Stats: Sharing ───────────────────────────────────

  /api/v1/publication/stats/reader-referrals:
    get:
      tags:
        - stats
      summary: Reader referral leaderboard
      description: |
        Returns a leaderboard of readers who have referred the most new visitors
        or subscribers. Each row includes the reader's identity and referral
        counts. Used in the Sharing tab of the publisher dashboard.
      operationId: getReaderReferrals
      security:
        - substackSession: []
      parameters:
        - in: query
          name: to
          required: true
          schema:
            type: string
            format: date
          description: End date for the referral window.
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
          description: Pagination offset.
        - in: query
          name: limit
          schema:
            type: integer
            default: 20
          description: Maximum number of results to return.
        - in: query
          name: order_by
          schema:
            type: string
            default: visitors
          description: Field to sort results by.
        - in: query
          name: order_direction
          schema:
            type: string
            default: desc
          description: Sort direction (asc or desc).
      responses:
        '200':
          description: Reader referral data.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Growth Stats ─────────────────────────────────────────────────

  /api/v1/publication/stats/growth/sources:
    get:
      tags:
        - growth
      summary: Hierarchical growth source breakdown
      description: |
        Returns a hierarchical breakdown of traffic and subscriber sources
        (Substack, Direct, Search, Social, Email) with nested children. Each
        source includes metrics like user counts and timeseries data.
      operationId: getGrowthSources
      security:
        - substackSession: []
      parameters:
        - in: query
          name: from_date
          required: true
          schema:
            type: string
            format: date
          description: Start date for the time range.
        - in: query
          name: to_date
          required: true
          schema:
            type: string
            format: date
          description: End date for the time range.
        - in: query
          name: order_by
          schema:
            type: string
            default: users
          description: Field to sort results by.
        - in: query
          name: order_direction
          schema:
            type: string
            default: desc
          description: Sort direction (asc or desc).
      responses:
        '200':
          description: Growth source breakdown.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  sourceMetrics:
                    type: array
                    items:
                      type: object
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/growth/partial-timeseries:
    post:
      tags:
        - growth
      summary: Growth source time series chart data
      description: |
        Accepts a list of selected growth sources and returns timeseries chart
        data for each. Used to render the visitor growth chart with selectable
        source overlays in the Growth section of the publisher dashboard.
      operationId: getGrowthPartialTimeseries
      security:
        - substackSession: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
              required:
                - sources
                - orderBy
                - orderDirection
              properties:
                sources:
                  type: array
                  items:
                    type: object
                  description: Array of source objects to include in the timeseries.
                orderBy:
                  type: string
                  description: Field to order by.
                orderDirection:
                  type: string
                  description: Sort direction (asc or desc).
                fromDate:
                  type: string
                  format: date
                  description: Optional start date for the time range.
                toDate:
                  type: string
                  format: date
                  description: Optional end date for the time range.
      responses:
        '200':
          description: Growth timeseries data.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  timeseries:
                    type: array
                    items:
                      type: object
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/growth/events:
    get:
      tags:
        - growth
      summary: Publication events for growth correlation
      description: |
        Returns a list of publication events (posts, notes, etc.) within a date
        range. Used to correlate publication activity with growth spikes shown
        on the Growth visitors chart.
      operationId: getGrowthEvents
      security:
        - substackSession: []
      parameters:
        - in: query
          name: from_date
          required: true
          schema:
            type: string
            format: date
          description: Start date for the event window.
        - in: query
          name: to_date
          required: true
          schema:
            type: string
            format: date
          description: End date for the event window.
      responses:
        '200':
          description: Publication events.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  pubEvents:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        date:
                          type: string
                          format: date
                        title:
                          type: string
                        slug:
                          type: string
                        type:
                          type: string
                        url:
                          type: string
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/publication/stats/emails/timeseries:
    get:
      tags:
        - dashboard
      summary: Email stats time series
      description: |
        Returns time-series email statistics (opens, clicks, sends) for the
        publication. Requires authentication and a publication-scoped host.
      operationId: getEmailStatsTimeseries
      security:
        - substackSession: []
      parameters:
        - in: query
          name: start_date
          schema:
            type: string
            format: date
          description: Start date for the time range.
        - in: query
          name: end_date
          schema:
            type: string
            format: date
          description: End date for the time range.
      responses:
        '200':
          description: Email stats time series data.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailStatsTimeseries'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/activity/unread:
    get:
      tags:
        - dashboard
      summary: Unread activity count
      description: |
        Returns the count of unread activity items for the authenticated user.
        Requires authentication.
      operationId: getUnreadActivity
      security:
        - substackSession: []
      responses:
        '200':
          description: Unread activity count.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnreadActivity'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/messages/unread-count:
    get:
      tags:
        - dashboard
      summary: Unread messages count
      description: |
        Returns the count of unread messages for the authenticated user.
        Requires authentication.
      operationId: getUnreadMessagesCount
      security:
        - substackSession: []
      responses:
        '200':
          description: Unread messages count.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnreadMessagesCount'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/publication_user:
    get:
      tags:
        - settings
      summary: List publication users
      description: |
        Returns the list of users associated with the publication, including
        their roles and visibility settings. Requires authentication and a
        publication-scoped host.
      operationId: getPublicationUsers
      security:
        - substackSession: []
      responses:
        '200':
          description: Publication user list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicationUsersResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/publication/sections:
    get:
      tags:
        - settings
        - publication
      summary: List publication sections
      description: |
        Returns the sections configured for the publication. Requires
        authentication and a publication-scoped host.
      operationId: getPublicationSections
      security:
        - substackSession: []
      responses:
        '200':
          description: Array of section objects.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Section'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/boost:
    get:
      tags:
        - settings
      summary: Get boost settings
      description: |
        Returns the publication's boost configuration, including whether boost
        is enabled and associated feature flags. Requires authentication and a
        publication-scoped host.
      operationId: getBoostSettings
      security:
        - substackSession: []
      responses:
        '200':
          description: Boost settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BoostSettings'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/publication_settings:
    get:
      tags:
        - settings
        - publication
      summary: Get publication settings
      description: |
        Returns the publication's settings and configuration. Requires
        authentication and a publication-scoped host.
      operationId: getPublicationSettings
      security:
        - substackSession: []
      responses:
        '200':
          description: Publication settings.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicationSettings'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/pledges/plans:
    get:
      tags:
        - dashboard
        - subscription
      summary: List pledge plans
      description: |
        Returns the payment pledge plans configured for the publication,
        including plan names, amounts, intervals, and currencies. Requires
        authentication and a publication-scoped host.
      operationId: getPledgePlans
      security:
        - substackSession: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PledgePlans'
          description: Returns available pledge plans.
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/pledges/plans/summary:
    get:
      tags:
        - dashboard
        - subscription
      summary: Pledge plans summary
      description: |
        Returns a summary of pledge plans including plan details, aggregate
        pledge statistics, and total pledge count. Requires authentication and
        a publication-scoped host.
      operationId: getPledgePlansSummary
      security:
        - substackSession: []
      responses:
        '200':
          description: Pledge plans summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PledgePlansSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/publication/bestseller_tier:
    get:
      tags:
        - dashboard
        - publication
      summary: Get publication bestseller tier
      description: |
        Returns the publication's current bestseller tier, category, and rank.
        Requires authentication and a publication-scoped host.
      operationId: getBestsellerTier
      security:
        - substackSession: []
      responses:
        '200':
          description: Bestseller tier information.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BestsellerTier'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/recommendations/{publicationId}:
    get:
      tags:
        - recommendations
      summary: Outgoing recommendations for a publication
      description: |
        Returns the list of publications that the given publication recommends.
        Supports paginated mode via `paginate=true` query parameter. Requires
        authentication.
      operationId: getRecommendations
      security:
        - substackSession: []
      parameters:
        - in: path
          name: publicationId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Publication ID to get recommendations for.
        - in: query
          name: paginate
          schema:
            type: boolean
            default: false
          description: Set to `true` for cursor-paginated results.
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: List of recommended publications.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/recommendations/stats/from:
    get:
      tags:
        - recommendations
      summary: Outgoing recommendation stats
      description: |
        Returns statistics about recommendations made by the authenticated
        user's publication (outgoing). Requires authentication.
      operationId: getOutgoingRecommendationStats
      security:
        - substackSession: []
      responses:
        '200':
          description: Outgoing recommendation statistics.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationStats'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/recommendations/stats/to:
    get:
      tags:
        - recommendations
      summary: Incoming recommendation stats
      description: |
        Returns statistics about recommendations pointing to the authenticated
        user's publication (incoming). Requires authentication.
      operationId: getIncomingRecommendationStats
      security:
        - substackSession: []
      responses:
        '200':
          description: Incoming recommendation statistics.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationStats'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/recommendations/exist:
    get:
      tags:
        - recommendations
      summary: Check if recommendations exist
      description: |
        Returns whether the authenticated user has any recommendations
        configured. Requires authentication.
      operationId: checkRecommendationsExist
      security:
        - substackSession: []
      responses:
        '200':
          description: Existence check result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationsExist'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/recommendations/{publicationId}/suggested:
    get:
      tags:
        - recommendations
      summary: Suggested recommendations for a publication
      description: |
        Returns a list of suggested publications to recommend based on the
        given publication. Requires authentication.
      operationId: getSuggestedRecommendations
      security:
        - substackSession: []
      parameters:
        - in: path
          name: publicationId
          required: true
          schema:
            type: integer
            minimum: 1
          description: Publication ID to get suggestions for.
      responses:
        '200':
          description: Suggested recommendations.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendationsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  parameters:
    PostIdPath:
      in: path
      name: postId
      required: true
      schema:
        type: integer
        minimum: 1
      description: Numeric post ID.
    CursorQuery:
      in: query
      name: cursor
      schema:
        type: string
      description: Opaque pagination cursor returned by a previous response.
  responses:
    Unauthorized:
      description: Authentication required or session expired.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    BadRequest:
      description: Malformed request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    Forbidden:
      description: Insufficient permissions (e.g. accessing another user's resource).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
  securitySchemes:
    substackSession:
      type: apiKey
      in: cookie
      name: substack.sid
      description: |
        Substack session cookie established by the standard browser sign-in flow.
        Copy the value of `substack.sid` from your authenticated browser session
        to authenticate API requests. The `substack-api` client sets this cookie
        automatically when given a `token` in `SubstackConfig`.
  schemas:
    ApiError:
      type: object
      description: Generic Substack API error envelope.
      properties:
        error:
          type: string
          description: Machine-readable error code.
        message:
          type: string
          description: Human-readable error message.
        status_code:
          type: integer
          description: HTTP status code of the error.
        details:
          type: object
          additionalProperties: true
          description: Additional error details as key-value pairs.
      additionalProperties: true
    Reactions:
      type: object
      description: Reaction counts keyed by emoji.
      additionalProperties:
        type: integer
        minimum: 0
      examples:
        - ❤: 12
          🔥: 4
    Byline:
      type: object
      description: Compact author byline (a denormalised user record).
      properties:
        id:
          type: integer
          description: Author's numeric user ID.
        name:
          type: string
          description: Author display name shown in post headers and byline areas.
        handle:
          type: string
          description: Author @handle used in profile URLs.
        photo_url:
          type: string
          format: uri
          description: URL of the author's avatar image, rendered as a circular thumbnail.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
        is_admin:
          type: boolean
          description: Whether the author is a publication admin.
      required:
        - id
        - name
        - handle
        - photo_url
      additionalProperties: true
    User:
      type: object
      description: Substack user record.
      properties:
        id:
          type: integer
          description: Unique numeric user ID.
        name:
          type: string
          description: Display name shown on profile and byline areas.
        handle:
          type: string
          description: Unique @handle used in profile URLs (e.g. `jakubslys`).
        previous_name:
          type: string
          description: Previous display name before the user changed it. Null if never changed.
        photo_url:
          type: string
          format: uri
          description: URL of the user's avatar/profile image.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
        profile_set_up_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the user completed profile setup.
        reader_installed_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the user first used the Substack reader app.
      required:
        - id
      additionalProperties: true
    PreviewPost:
      type: object
      description: Lightweight post preview returned by listing endpoints.
      properties:
        id:
          type: integer
          description: Unique numeric post ID.
        title:
          type: string
          description: Post title, shown as heading and in preview cards.
        subtitle:
          type: string
          description: Secondary text shown below the post title in preview cards.
        slug:
          type: string
          description: URL slug for the post (used in the canonical URL path, e.g. `ai-risks`).
        post_date:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the post was published, shown in the metadata line.
        canonical_url:
          type: string
          format: uri
          description: Canonical URL of the post.
        type:
          type: string
          description: 'Content type: `newsletter` or `podcast`.'
        truncated_body_text:
          type: string
          description: Plain-text body excerpt for previews and search results.
        cover_image:
          type: string
          format: uri
          description: URL of the post cover image.
        audience:
          type: string
          description: 'Visibility: `everyone` for public, `only_paid` for paywalled.'
        publication_id:
          type: integer
        reactions:
          $ref: '#/components/schemas/Reactions'
          description: Reaction counts keyed by emoji.
        comment_count:
          type: integer
          description: Number of comments, displayed with a comment icon in the action bar.
      examples:
        - id: 123456
          title: The Future of Remote Work
          slug: the-future-of-remote-work
          post_date: '2024-01-15T09:00:00Z'
          canonical_url: https://example.substack.com/p/the-future-of-remote-work
          type: newsletter
          cover_image: https://example.substack.com/p/image.jpg
          audience: everyone
          publication_id: 98765
          reactions:
            ❤: 42
            🔥: 7
          comment_count: 12
      required:
        - id
        - title
      additionalProperties: true
    FullPost:
      description: Full post payload as returned by `GET /api/v1/posts/by-id/{id}`.
      allOf:
        - $ref: '#/components/schemas/PreviewPost'
        - type: object
          properties:
            body_html:
              type: string
              description: Rendered HTML body. Only present for posts the caller has access to (free posts, or paywalled posts when authenticated as a subscriber).
            htmlBody:
              type: string
            description:
              type: string
            wordcount:
              type: integer
            podcast_url:
              type: string
              format: uri
            publishedBylines:
              type: array
              items:
                $ref: '#/components/schemas/Byline'
            reaction_count:
              type: integer
            restacks:
              type: integer
            child_comment_count:
              type: integer
            postTags:
              type: array
              items:
                type: string
          additionalProperties: true
    PublicationPost:
      type: object
      description: |
        Post as returned by publication-scoped endpoints (`/archive`, `/posts`,
        homepage). Shape varies depending on whether `body_html` is included.
      properties:
        id:
          type: integer
          description: Unique numeric post ID.
        publication_id:
          type: integer
        title:
          type: string
          description: Post title shown as a clickable heading in listing cards.
        slug:
          type: string
          description: URL slug for the post.
        post_date:
          type: string
          format: date-time
          description: ISO 8601 timestamp of publication, shown in the post metadata line.
        canonical_url:
          type: string
          format: uri
          description: Canonical URL of the post.
        cover_image:
          type: string
          format: uri
          description: URL of the post cover/thumbnail image.
        type:
          type: string
          description: Content type (`newsletter`, `podcast`, etc.).
        audience:
          type: string
          description: Audience restriction. `everyone` for public, `only_paid` for paywalled.
        body_html:
          type: string
          description: Rendered HTML (present for `/api/v1/posts`, absent for `/archive`).
        truncated_body_text:
          type: string
          description: Plain-text preview excerpt shown below the title in listing cards.
        wordcount:
          type: integer
          description: Word count of the post body, used to calculate reading time estimates.
        comment_count:
          type: integer
          description: Number of comments on the post.
        reactions:
          $ref: '#/components/schemas/Reactions'
          description: Reaction counts keyed by emoji.
        publishedBylines:
          type: array
          items:
            $ref: '#/components/schemas/Byline'
          description: Author bylines shown in the post header with name, photo, and profile link.
      required:
        - id
        - title
        - slug
        - post_date
        - canonical_url
      additionalProperties: true
    Post:
      description: |
        Generic post record. Substack returns post payloads in three concrete
        shapes depending on the endpoint family:

        - `PreviewPost` for profile feeds (`/api/v1/profile/posts`).
        - `FullPost` for the by-id lookup (`/api/v1/posts/by-id/{id}`).
        - `PublicationPost` for publication-scoped endpoints (`/archive`,
          `/posts`, homepage).

        Use this alias when a consumer wants to type a value that may carry
        any of the three shapes (e.g. when normalizing across feeds).
      oneOf:
        - $ref: '#/components/schemas/PreviewPost'
        - $ref: '#/components/schemas/FullPost'
        - $ref: '#/components/schemas/PublicationPost'
    PaginatedPosts:
      type: array
      description: |
        Array of full publication posts. The Substack endpoint returns a bare
        array; pagination is offset-based via the `offset` and `limit` query
        parameters. The `substack-api` client wraps this into an async
        iterator that stops when fewer than `limit` items are returned.
      items:
        $ref: '#/components/schemas/PublicationPost'
    Section:
      type: object
      description: A section / category within a publication.
      properties:
        id:
          type: integer
          description: Unique numeric section ID.
        publication_id:
          type: integer
        name:
          type: string
          description: Section display name shown as a navigation tab in the publication header.
        slug:
          type: string
          description: URL slug for the section (used in navigation links).
        description:
          type: string
          description: Section description text.
        hidden:
          type: boolean
          description: Whether the section is hidden from the publication's navigation bar.
      additionalProperties: true
    HomepageData:
      type: object
      description: Publication homepage payload.
      properties:
        newPosts:
          type: array
          items:
            $ref: '#/components/schemas/PublicationPost'
          description: Recent posts displayed on the homepage.
        pinnedPosts:
          type: array
          items:
            $ref: '#/components/schemas/PublicationPost'
          description: Posts pinned to the top of the homepage.
        sections:
          type: array
          items:
            $ref: '#/components/schemas/Section'
          description: Publication sections defining the navigation tabs.
        publicationName:
          type: string
          description: Display name of the publication, shown in the header navbar and browser title.
        publicationLogoUrl:
          type: string
          format: uri
          description: URL of the publication logo displayed in the header navbar.
        publicationCustomDomain:
          type: string
          description: Custom domain (e.g. `www.example.com`). Null if using Substack default.
      additionalProperties: true
    NoteAttachment:
      type: object
      description: |
        A note attachment — typically a link preview, but can be an image or
        embedded media. Attachments are created via `POST /api/v1/comment/attachment/`
        and referenced from the publish payload.
      properties:
        id:
          oneOf:
            - type: integer
            - type: string
          description: Unique attachment identifier (UUID format).
        type:
          type: string
          enum:
            - link
            - image
            - video
            - audio
            - embed
          description: Attachment type. `image` for image attachments, `post` for post link previews.
        url:
          type: string
          format: uri
          description: URL of the linked content.
        image_url:
          type: string
          format: uri
          description: URL of the attachment preview image.
        title:
          type: string
          description: Title of the linked content.
        description:
          type: string
          description: Description of the linked content.
        host:
          type: string
          description: Hostname of the linked URL.
        favicon_url:
          type: string
          format: uri
          description: URL of the linked site's favicon.
      additionalProperties: true
    NoteUser:
      type: object
      description: A user referenced in a note context.
      properties:
        id:
          type: integer
          description: Unique numeric user ID.
        name:
          type: string
          description: Display name shown above the note.
        handle:
          type: string
          description: Unique @handle.
        photo_url:
          type: string
          format: uri
          description: URL of the user's avatar image shown as a circular thumbnail.
        previous_name:
          type: string
          description: Previous display name. Null if never changed.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
        profile_set_up_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of profile setup.
        reader_installed_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of first reader app use.
        bestseller_tier:
          type: string
          description: Substack bestseller tier. Null if not a bestseller.
        status:
          type: object
          additionalProperties: true
          description: User status info (e.g. `payments_state`, `pledges_enabled`).
        primary_publication:
          type: object
          additionalProperties: true
          description: User's primary publication, shown as a link under their name in the notes feed.
      required:
        - id
        - name
        - handle
        - photo_url
      additionalProperties: true
    NoteComment:
      type: object
      description: The nested comment object inside a note feed envelope.
      properties:
        id:
          type: integer
          description: Unique numeric comment ID.
        body:
          type: string
          description: Plain-text body content of the note.
        user_id:
          type: integer
        type:
          type: string
          description: Comment type. `feed` for standalone notes.
        date:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the note was posted.
        name:
          type: string
          description: Author display name shown above the note.
        reaction_count:
          type: integer
          description: Number of likes/hearts on the note.
        reactions:
          $ref: '#/components/schemas/Reactions'
          description: Reaction counts keyed by emoji.
        restacks:
          type: integer
          description: Number of restacks (shares to Substack Notes).
        restacked:
          type: boolean
          description: Whether the current user has restacked this note.
        children_count:
          type: integer
          description: Number of direct replies to the note.
        language:
          type: string
          description: ISO 639-1 language code of the note content.
        body_json:
          type: object
          additionalProperties: true
          description: ProseMirror JSON document containing the note's formatted content.
        publication_id:
          type: integer
          description: Publication ID if posted on behalf of a publication. Null for standalone notes.
        post_id:
          type: integer
          description: Post ID if the note references a specific post. Null otherwise.
        edited_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of last edit. Null if never edited.
        ancestor_path:
          type: string
          description: Dot-separated path of parent comment IDs. Empty string for root-level notes.
        reply_minimum_role:
          type: string
          description: Minimum role required to reply. `everyone` for public notes.
        media_clip_id:
          type: string
          description: ID of an attached media clip. Null if none.
        photo_url:
          type: string
          format: uri
          description: URL of the author's avatar image.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
        handle:
          type: string
          description: Author @handle shown next to their name.
        user_bestseller_tier:
          type: string
          description: Substack bestseller tier of the note author. Null if not applicable.
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/NoteAttachment'
          description: Attached media (images, post links) shown below the note content.
        userStatus:
          type: object
          additionalProperties: true
          description: Status object of the note author (e.g. `payments_state`).
        user_primary_publication:
          type: object
          additionalProperties: true
          description: Primary publication of the note author, shown as a link under their name.
        autotranslate_to:
          type: string
          description: Target language code for auto-translation. Null if not translated.
        tracking_parameters:
          type: object
          additionalProperties: true
          description: Analytics/tracking parameters for the note.
      examples:
        - id: 987654
          body: Great insights on remote work!
          user_id: 12345
          type: comment
          date: '2024-01-15T10:30:00Z'
          name: Jane Doe
          reaction_count: 5
          reactions:
            ❤: 3
            👏: 2
          restacks: 1
          children_count: 2
          photo_url: https://substackcdn.com/image.jpg
          handle: janedoe
      required:
        - id
        - body
      additionalProperties: true
    Note:
      type: object
      description: |
        A Substack note feed envelope. Notes are returned as wrapper objects
        with `entity_key` and `type`, plus optional nested `comment`, `context`,
        and `parentComments` fields.
      properties:
        entity_key:
          type: string
          description: 'Unique feed item key. Format: `c-{commentId}` for notes, `p-{postId}` for posts.'
        type:
          type: string
          description: Feed item type. `comment` for notes.
        context:
          type: object
          properties:
            type:
              type: string
            timestamp:
              type: string
              format: date-time
            users:
              type: array
              items:
                $ref: '#/components/schemas/NoteUser'
            fallbackReason:
              type: string
            fallbackUrl:
              type: string
              format: uri
            isFresh:
              type: boolean
            source:
              type: string
            page:
              type: string
            page_rank:
              type: integer
          additionalProperties: true
          description: Origin, associated users, and retrieval metadata for the note.
        publication:
          type: object
          additionalProperties: true
          description: Associated publication. Null for standalone notes.
        post:
          type: object
          additionalProperties: true
          description: Associated post. Null for standalone notes.
        comment:
          $ref: '#/components/schemas/NoteComment'
          description: The note/comment content object.
        parentComments:
          type: array
          items:
            $ref: '#/components/schemas/NoteComment'
          description: Parent comments in the thread.
        isMuted:
          type: boolean
          description: Whether the current user has muted this thread.
        canReply:
          type: boolean
          description: Whether the current user can reply to this note.
        trackingParameters:
          type: object
          additionalProperties: true
          description: Analytics/tracking parameters for the feed item.
      examples:
        - entity_key: note-abc123
          type: note
          context:
            type: recent
            timestamp: '2024-01-15T12:00:00Z'
            users:
              - id: 12345
                name: Jane Doe
                handle: janedoe
                photo_url: https://substackcdn.com/image.jpg
          comment:
            id: 987654
            body: Excited to share my latest post!
            user_id: 12345
            type: comment
            date: '2024-01-15T12:00:00Z'
            name: Jane Doe
            reaction_count: 8
            reactions:
              ❤: 5
              🔥: 3
            children_count: 2
      required:
        - entity_key
        - type
      additionalProperties: true
    Comment:
      type: object
      description: A comment on a post. Flat object with no inheritance from Note.
      properties:
        id:
          type: integer
          description: Unique numeric comment ID.
        body:
          type: string
          description: Plain-text body content of the comment.
        user_id:
          type: integer
        type:
          type: string
          description: Comment type (e.g. `feed`).
        date:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the comment was posted.
        name:
          type: string
          description: Author display name shown as bold text above the comment.
        reaction_count:
          type: integer
          description: Number of likes/hearts on the comment.
        reactions:
          $ref: '#/components/schemas/Reactions'
          description: Reaction counts keyed by emoji.
        restacks:
          type: integer
          description: Number of restacks (shares to Notes).
        restacked:
          type: boolean
          description: Whether the current user has restacked this comment.
        children_count:
          type: integer
          description: Number of direct replies.
        body_json:
          type: object
          additionalProperties: true
          description: ProseMirror JSON document (schema version `v1`) for rich-text comments.
        publication_id:
          type: integer
          description: Publication ID if the comment is on a publication post.
        post_id:
          type: integer
          description: Post ID the comment belongs to.
        edited_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of last edit. Null if never edited.
        ancestor_path:
          type: string
          description: Dot-separated parent comment IDs for thread nesting. Empty for root-level.
        reply_minimum_role:
          type: string
          description: 'Minimum role to reply: `everyone` or `only_paid`.'
        media_clip_id:
          type: string
          description: ID of an attached media clip. Null if none.
        photo_url:
          type: string
          format: uri
          description: URL of the author's avatar shown as a circular image next to the comment.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
        handle:
          type: string
          description: Author @handle shown next to their name.
        user_bestseller_tier:
          type: string
          description: Substack bestseller tier of the comment author. Null if not applicable.
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/NoteAttachment'
          description: Attached media (images, post links).
        userStatus:
          type: object
          additionalProperties: true
          description: Status object of the comment author.
        user_primary_publication:
          type: object
          additionalProperties: true
          description: Primary publication of the comment author, shown as a link under their name.
        language:
          type: string
          description: ISO 639-1 language code.
        autotranslate_to:
          type: string
          description: Target language code for auto-translation. Null if not translated.
        tracking_parameters:
          type: object
          additionalProperties: true
          description: Analytics/tracking parameters.
        author_is_admin:
          type: boolean
          description: Whether the comment author is a publication admin.
      required:
        - id
        - body
      additionalProperties: true
    CommentResponse:
      type: object
      description: |
        Wrapper returned by `GET /api/v1/reader/comment/{id}`. Substack
        double-wraps the payload — the outer `item` holds an inner object
        whose `comment` property is the actual comment record.
      properties:
        item:
          type: object
          properties:
            comment:
              $ref: '#/components/schemas/Comment'
          additionalProperties: true
          description: The comment feed item containing the comment and its context.
      additionalProperties: true
    PaginatedComments:
      type: object
      description: Comments on a post, with a `more` flag for additional pages.
      properties:
        comments:
          type: array
          items:
            $ref: '#/components/schemas/Comment'
        more:
          type: boolean
          description: True if more pages of comments exist.
      additionalProperties: true
    CommentRepliesResponse:
      type: object
      description: Threaded reply branches under a comment.
      properties:
        commentBranches:
          type: array
          items:
            type: object
            properties:
              comment:
                $ref: '#/components/schemas/Comment'
              descendantComments:
                type: array
                items:
                  type: object
                  properties:
                    type:
                      type: string
                    comment:
                      $ref: '#/components/schemas/Comment'
                  required:
                    - type
                    - comment
                  additionalProperties: true
            required:
              - comment
              - descendantComments
            additionalProperties: true
          description: Comment reply branches (thread subtrees).
        moreBranches:
          type: integer
          description: Count of additional reply branches not included in this response.
        nextCursor:
          oneOf:
            - type: string
            - type: 'null'
          description: Opaque pagination cursor for the next page of replies. Null if no more pages.
      required:
        - commentBranches
        - moreBranches
        - nextCursor
      additionalProperties: true
    PotentialHandles:
      type: object
      description: Handle options for the authenticated user.
      properties:
        potentialHandles:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              handle:
                type: string
              type:
                type: string
                description: Handle type discriminator (e.g. `user`, `publication`).
            required:
              - id
              - handle
              - type
            additionalProperties: true
          description: Suggested handle strings based on the user's name.
      required:
        - potentialHandles
      additionalProperties: true
    UserProfile:
      type: object
      description: |
        Lightweight profile fields extracted from the no-parameters call to
        `/api/v1/reader/feed/profile/{profileId}`. The raw endpoint returns a
        feed wrapper; the client extracts `id` and `handle` from the first
        `context.users` entry and then resolves the full profile via
        `getProfileBySlug`. This schema documents the conceptual profile
        record, not the raw wire format.
      properties:
        id:
          type: integer
          description: Unique numeric user ID.
        name:
          type: string
        handle:
          type: string
          description: Unique @handle.
        photo_url:
          type: string
          format: uri
          description: URL of the user's avatar image.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
      required:
        - id
      additionalProperties: true
    Profile:
      type: object
      description: |
        Public-profile fields shared by both authenticated and anonymous
        profile lookups. `FullProfile` extends this with extra signed-in
        fields.
      properties:
        id:
          type: integer
          description: Unique numeric user ID.
        name:
          type: string
          description: Display name shown on the profile page.
        handle:
          type: string
          description: Unique @handle used in profile URLs.
        slug:
          type: string
          description: URL slug for the profile (same as handle).
        photo_url:
          type: string
          format: uri
          description: URL of the profile avatar image.
        cover_photo_url:
          type: string
          format: uri
          description: URL of the profile cover/banner image shown at the top of the profile page.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
        url:
          type: string
          format: uri
        twitter_screen_name:
          type: string
          description: Twitter/X handle if connected. Null if not linked.
        publicationUsers:
          type: array
          items:
            $ref: '#/components/schemas/PublicationUser'
          description: Publication-user relationships showing which publications the user belongs to.
      required:
        - id
        - name
        - handle
      additionalProperties: true
    FullProfile:
      description: |
        Full profile with extra fields exposed by
        `GET /api/v1/user/{slug}/public_profile`.
      allOf:
        - $ref: '#/components/schemas/Profile'
        - type: object
          properties:
            tos_accepted_at:
              oneOf:
                - type: string
                  format: date-time
                - type: 'null'
            profile_disabled:
              oneOf:
                - type: boolean
                - type: 'null'
            userLinks:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: integer
                  user_id:
                    type: integer
                  url:
                    type: string
                    format: uri
                  title:
                    type: string
                additionalProperties: true
            subscriptions:
              type: array
              items:
                type: object
                properties:
                  publication_id:
                    type: integer
                  user_id:
                    type: integer
                  created_at:
                    type: string
                    format: date-time
                  type:
                    type: string
                additionalProperties: true
            subscriptionsTruncated:
              oneOf:
                - type: boolean
                - type: 'null'
            hasGuestPost:
              oneOf:
                - type: boolean
                - type: 'null'
            primaryPublication:
              oneOf:
                - $ref: '#/components/schemas/PrimaryPublication'
                - type: 'null'
            max_pub_tier:
              oneOf:
                - type: integer
                - type: 'null'
            hasActivity:
              oneOf:
                - type: boolean
                - type: 'null'
            hasLikes:
              oneOf:
                - type: boolean
                - type: 'null'
            lists:
              type: array
              items:
                type: object
            rough_num_free_subscribers_int:
              oneOf:
                - type: integer
                - type: 'null'
            rough_num_free_subscribers:
              oneOf:
                - type: string
                - type: 'null'
            bestseller_badge_disabled:
              oneOf:
                - type: boolean
                - type: 'null'
            subscriberCountString:
              oneOf:
                - type: string
                - type: 'null'
            subscriberCount:
              oneOf:
                - type: string
                - type: 'null'
            subscriberCountNumber:
              oneOf:
                - type: integer
                - type: 'null'
            hasHiddenPublicationUsers:
              oneOf:
                - type: boolean
                - type: 'null'
            visibleSubscriptionsCount:
              oneOf:
                - type: integer
                - type: 'null'
            previousSlug:
              oneOf:
                - type: string
                - type: 'null'
            primaryPublicationIsPledged:
              oneOf:
                - type: boolean
                - type: 'null'
            primaryPublicationSubscriptionState:
              oneOf:
                - type: string
                - type: 'null'
            isSubscribed:
              oneOf:
                - type: boolean
                - type: 'null'
            isFollowing:
              oneOf:
                - type: boolean
                - type: 'null'
            followsViewer:
              oneOf:
                - type: boolean
                - type: 'null'
            can_dm:
              oneOf:
                - type: boolean
                - type: 'null'
            dm_upgrade_options:
              type: array
              items:
                type: string
          additionalProperties: true
    ProfileSearchResult:
      type: object
      description: A single profile search result.
      properties:
        id:
          type: integer
          description: Unique numeric user ID.
        name:
          type: string
        handle:
          type: string
          description: Unique @handle.
        photo_url:
          type: string
          format: uri
          description: URL of the user's avatar.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
      required:
        - id
        - name
        - handle
      additionalProperties: true
    ProfileSearchResponse:
      type: object
      description: Profile search results page.
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/ProfileSearchResult'
          description: Matching profile search results.
        more:
          type: boolean
          description: Whether additional search results are available beyond this page.
      additionalProperties: true
    Publication:
      type: object
      description: A Substack publication (newsletter).
      properties:
        id:
          type: integer
          description: Unique numeric publication ID.
        name:
          type: string
          description: Publication display name shown in the header navbar and browser title.
        subdomain:
          type: string
          description: Substack subdomain (the segment before `.substack.com`).
        custom_domain:
          oneOf:
            - type: string
            - type: 'null'
          description: Custom domain (e.g. `www.example.com`). Null if using default `*.substack.com`.
        custom_domain_optional:
          type: boolean
          description: Whether the publication can operate without a custom domain.
        hero_text:
          type: string
          description: Publication tagline displayed below the name in the hero section.
        logo_url:
          type: string
          format: uri
          description: URL of the publication logo displayed in the header navbar.
        author_id:
          type: integer
        theme_var_background_pop:
          type: string
          description: Hex color for the publication's accent/highlight theme color (e.g. `#2EE240`).
        community_enabled:
          type: boolean
          description: Whether the publication has community (chat/comments) features enabled.
        copyright:
          type: string
          description: Copyright notice text shown in the footer.
        founding_plan_name:
          type: string
          description: Display name of the founding member tier (e.g. `Founding Member`).
        community_url:
          type: string
          format: uri
          description: URL of the publication's community page.
      required:
        - id
        - name
      additionalProperties: true
    PublicationUser:
      type: object
      description: Relationship between a user and a publication.
      properties:
        id:
          type: integer
          description: Unique relationship ID.
        user_id:
          type: integer
        publication_id:
          type: integer
        role:
          type: string
          description: Role within the publication (e.g. `admin`, `writer`).
        public:
          type: boolean
          description: Whether this relationship is publicly visible on the profile.
        is_primary:
          type: boolean
          description: Whether this is the user's primary publication.
        publication:
          type: object
          additionalProperties: true
          description: The publication for this relationship.
      additionalProperties: true
    PrimaryPublication:
      type: object
      description: The primary publication for a user profile.
      properties:
        id:
          type: integer
          description: Unique numeric publication ID.
        name:
          type: string
          description: Publication display name.
        subdomain:
          type: string
          description: Substack subdomain.
        custom_domain:
          oneOf:
            - type: string
            - type: 'null'
          description: Custom domain. Null if using default Substack domain.
        logo_url:
          oneOf:
            - type: string
              format: uri
            - type: 'null'
          description: URL of the publication logo.
        author_id:
          oneOf:
            - type: integer
            - type: 'null'
        handles_enabled:
          oneOf:
            - type: boolean
            - type: 'null'
          description: Whether custom user handles are enabled for this publication.
      required:
        - id
        - name
        - subdomain
      additionalProperties: true
    Category:
      type: object
      description: A Substack content category with optional subcategories.
      properties:
        id:
          oneOf:
            - type: integer
            - type: string
          description: Unique numeric category ID.
        name:
          type: string
          description: Category display name.
        canonical_name:
          type: string
          description: Normalized machine-readable name used for matching and routing.
        active:
          type: boolean
          description: Whether this category is currently active and visible.
        rank:
          type: number
          description: Sort rank for ordering categories in listings.
        slug:
          type: string
          description: URL slug for the category page.
        subcategories:
          oneOf:
            - type: array
              items:
                $ref: '#/components/schemas/Subcategory'
            - type: 'null'
          description: May be `null` when a category has no subcategories.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of category creation.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of last update.
        parent_tag_id:
          oneOf:
            - type: integer
            - type: 'null'
          description: The parent category. Null for top-level categories.
        emoji:
          oneOf:
            - type: string
            - type: 'null'
          description: Emoji icon associated with the category.
        leaderboard_description:
          oneOf:
            - type: string
            - type: 'null'
          description: Description text shown on the category leaderboard page.
        deprecated:
          type: boolean
          description: Whether this category is deprecated and no longer shown.
      required:
        - id
        - name
        - canonical_name
        - active
        - rank
        - slug
        - subcategories
      additionalProperties: true
    Subcategory:
      type: object
      description: A subcategory within a content category.
      properties:
        id:
          oneOf:
            - type: integer
            - type: string
          description: Unique numeric subcategory ID.
        name:
          type: string
          description: Subcategory display name.
        canonical_name:
          type: string
          description: Normalized machine-readable name.
        active:
          type: boolean
          description: Whether this subcategory is currently active.
        rank:
          type: number
          description: Sort rank for ordering.
        parent_tag_id:
          oneOf:
            - type: integer
            - type: 'null'
        slug:
          type: string
          description: URL slug for the subcategory.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of creation.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of last update.
        emoji:
          oneOf:
            - type: string
            - type: 'null'
          description: Emoji icon for the subcategory.
        leaderboard_description:
          oneOf:
            - type: string
            - type: 'null'
          description: Description shown on the leaderboard page.
        deprecated:
          type: boolean
          description: Whether this subcategory is deprecated.
      required:
        - id
        - name
        - canonical_name
        - active
        - rank
        - parent_tag_id
        - slug
      additionalProperties: true
    CategoryPublication:
      type: object
      description: |
        A publication listed under a category. Distinct from the general
        `Publication` schema — category listings expose fewer fields.
      properties:
        author_id:
          type: integer
        name:
          type: string
          description: Publication display name.
        subdomain:
          type: string
          description: Substack subdomain.
        logo_url:
          type: string
          format: uri
          description: URL of the publication logo.
        cover_photo_url:
          type: string
          format: uri
          description: URL of the publication cover/banner image.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of publication creation.
        custom_domain:
          oneOf:
            - type: string
            - type: 'null'
          description: Custom domain. Null if using default.
      required:
        - author_id
        - name
        - subdomain
      additionalProperties: true
    CategoryPublications:
      type: object
      description: Publications page for a category.
      properties:
        publications:
          type: array
          items:
            $ref: '#/components/schemas/CategoryPublication'
          description: Publications in this category.
        more:
          type: boolean
          description: Whether more publications are available.
      additionalProperties: true
    InboxItemAuthor:
      type: object
      description: Author/byline in an inbox item.
      properties:
        id:
          type: integer
          description: Unique numeric user ID.
        name:
          type: string
        handle:
          oneOf:
            - type: string
            - type: 'null'
          description: Unique @handle. Null if not set.
        photo_url:
          type: string
          format: uri
          description: URL of the author's avatar.
        previous_name:
          oneOf:
            - type: string
            - type: 'null'
          description: Previous display name. Null if never changed.
        bio:
          type: string
          description: Short biography displayed on the author's profile page.
        profile_set_up_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of profile setup.
        reader_installed_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of first reader use.
        publicationUsers:
          type: array
          items:
            type: object
          description: The author's publication relationships.
        is_guest:
          type: boolean
          description: Whether the author is a guest contributor.
        bestseller_tier:
          oneOf:
            - type: integer
            - type: 'null'
          description: Substack bestseller tier. Null if not applicable.
        status:
          type: object
          additionalProperties: true
          description: Author status metadata. Structure varies; typically null or absent.
      required:
        - id
        - name
        - handle
        - photo_url
      additionalProperties: true
    InboxItem:
      type: object
      description: |
        A single feed item from `/inbox/top`, `/reader/feed`, or `/top/search`.
        Represents a flat post object (not a polymorphic wrapper).
      properties:
        post_id:
          type: integer
        type:
          type: string
          description: Feed item type identifier.
        title:
          type: string
          description: Post title shown in the feed card.
        web_url:
          type: string
          format: uri
          description: Full URL to view the item on the web.
        content_key:
          type: string
          description: Internal key identifying the content item for the reader.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of last update.
        content_date:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of the content's original publication.
        inbox_date:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of when the item appeared in the inbox.
        seen_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of when the user last viewed this item. Null if unread.
        saved_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of when the user saved/bookmarked this item. Null if unsaved.
        archived_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of when the user archived this item. Null if not archived.
        skip_inbox:
          type: boolean
          description: Whether to skip showing this item in the inbox.
        extra_views:
          type: array
          items:
            type: object
          description: Additional view entries for cross-posted or section-level content. Typically empty.
        subtitle:
          oneOf:
            - type: string
            - type: 'null'
          description: Post subtitle shown in the feed card.
        cover_photo_url:
          oneOf:
            - type: string
              format: uri
            - type: 'null'
          description: URL of the cover image shown in the feed card.
        detail_view_subtitle:
          oneOf:
            - type: string
            - type: 'null'
          description: Subtitle shown in the detail/expanded view.
        audience:
          type: string
          description: Audience restriction. `everyone` for public, `only_paid` for paywalled.
        postType:
          type: string
          description: Post type identifier (e.g. `newsletter`).
        is_preview:
          type: boolean
          description: Whether this is a free preview of paywalled content.
        video_id:
          oneOf:
            - type: integer
            - type: string
            - type: 'null'
          description: ID of an embedded video. Null if none.
        audio_url:
          oneOf:
            - type: string
              format: uri
            - type: 'null'
          description: URL of the audio version (podcast/voiceover). Null if none.
        audio_type:
          oneOf:
            - type: string
            - type: 'null'
          description: Type of audio content.
        authors:
          type: array
          items:
            type: string
          description: Authors of the feed item.
        published_bylines:
          type: array
          items:
            $ref: '#/components/schemas/InboxItemAuthor'
          description: Byline objects for the published authors.
        publication_id:
          oneOf:
            - type: integer
            - type: 'null'
          description: ID of the publishing publication.
        publisher_name:
          oneOf:
            - type: string
            - type: 'null'
          description: Name of the publishing publication.
        publisher_image_url:
          oneOf:
            - type: string
              format: uri
            - type: 'null'
          description: Logo/image URL of the publisher.
        like_count:
          oneOf:
            - type: integer
            - type: 'null'
          description: Number of likes.
        comment_count:
          oneOf:
            - type: integer
            - type: 'null'
          description: Number of comments.
        read_progress:
          type: number
          description: Reading progress percentage (0 to 100).
        max_read_progress:
          type: number
          description: Maximum reading progress percentage achieved (0 to 100).
        audio_progress:
          type: number
          description: Audio playback progress percentage (0 to 100).
        max_audio_progress:
          type: number
          description: Maximum audio playback progress percentage achieved (0 to 100).
        video_progress:
          type: number
          description: Video playback progress percentage (0 to 100).
        max_video_progress:
          type: number
          description: Maximum video playback progress percentage achieved (0 to 100).
        is_personal_mode:
          type: boolean
          description: Whether the inbox is in personal mode.
        is_saved:
          type: boolean
          description: Whether the user has saved/bookmarked this item.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of creation.
        uuid:
          type: string
          description: Unique identifier in UUID format.
        coverImagePalette:
          type: object
          additionalProperties: true
          description: Dominant color palette extracted from the cover image for UI theming.
        duration_metadata:
          oneOf:
            - type: object
              properties:
                word_count:
                  oneOf:
                    - type: integer
                    - type: 'null'
                audio_duration:
                  oneOf:
                    - type: integer
                    - type: 'null'
            - type: 'null'
          description: Duration information for audio/video content.
      required:
        - post_id
        - type
        - title
        - web_url
      additionalProperties: true
    PaginatedFeed:
      type: object
      description: |
        Paginated feed envelope returned by cursor-paginated discovery
        endpoints such as `/reader/feed`, `/top/search`, and
        `/search/explore/web`. The TypeScript client normalises this into
        `{ items: FeedItem[], nextCursor: string | null }`.
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/InboxItem'
        nextCursor:
          oneOf:
            - type: string
            - type: 'null'
          description: Opaque pagination cursor for the next page. Null if no more pages.
      additionalProperties: true
    TopInboxFeed:
      type: object
      description: |
        Raw response from `GET /api/v1/inbox/top`. Returns an `inboxItems`
        array rather than the `items`/`nextCursor` shape used by other
        feed endpoints.
      properties:
        inboxItems:
          type: array
          items:
            $ref: '#/components/schemas/InboxItem'
      additionalProperties: true
    Facepile:
      type: object
      description: Reactor list for a post.
      properties:
        reactors:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
              name:
                type: string
              photo_url:
                type: string
                format: uri
            required:
              - id
              - name
            additionalProperties: true
          description: Users who reacted, shown as overlapping avatar thumbnails.
      required:
        - reactors
      additionalProperties: true
    LiveStream:
      type: object
      description: Live stream record.
      properties:
        id:
          type: integer
          description: Unique numeric stream ID.
        publication_id:
          type: integer
          description: ID of the hosting publication.
        status:
          type: string
          description: Stream status (e.g. `active`, `ended`).
        title:
          type: string
          description: Stream title shown in the listing.
        started_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the stream started.
        ended_at:
          oneOf:
            - type: string
              format: date-time
            - type: 'null'
          description: ISO 8601 timestamp of when the stream ended. Null if still active.
        livestream_url:
          type: string
          format: uri
          description: URL of the live stream.
      additionalProperties: true
    LiveStreamResponse:
      type: object
      description: |
        Active live-stream payload. Returns an empty object when no live
        stream is active.
      properties:
        activeLiveStream:
          oneOf:
            - $ref: '#/components/schemas/LiveStream'
            - type: 'null'
          description: The currently active live stream. Null if none.
      additionalProperties: true
    SubscriberLists:
      type: object
      description: |
        Subscriber-list payload. `subscriberLists` groups the publications/users
        the authenticated user follows.
      properties:
        subscriberLists:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              groups:
                type: array
                items:
                  type: object
                  properties:
                    users:
                      type: array
                      items:
                        type: object
                        properties:
                          id:
                            type: integer
                          handle:
                            type: string
                        required:
                          - id
                          - handle
                        additionalProperties: true
                  required:
                    - users
                  additionalProperties: true
            required:
              - id
              - name
              - groups
            additionalProperties: true
      required:
        - subscriberLists
      additionalProperties: true
    PublishNoteRequest:
      type: object
      description: Body for `POST /api/v1/comment/feed/`.
      properties:
        bodyJson:
          type: object
          description: ProseMirror JSON document.
          additionalProperties: true
        tabId:
          type: string
          description: Unique tab identifier for the note session.
        surface:
          type: string
          description: Surface context where the note is published.
        attachmentIds:
          type: array
          items:
            type: string
          description: Attachment IDs returned by `POST /api/v1/comment/attachment/`.
        replyMinimumRole:
          type: string
          enum:
            - everyone
          description: Who can reply to the note. Currently only `everyone` is supported.
      examples:
        - bodyJson:
            type: doc
            content:
              - type: paragraph
                content:
                  - type: text
                    text: Hello from the Substack API!
          tabId: tab-abc123
          surface: feed
          replyMinimumRole: everyone
      required:
        - bodyJson
        - tabId
        - surface
      additionalProperties: true
    PublishNoteResponse:
      type: object
      description: Published note record.
      properties:
        id:
          type: integer
        body:
          type: string
          description: Plain-text body of the published note.
        body_json:
          type: object
          additionalProperties: true
          description: ProseMirror JSON document of the note content.
        date:
          type: string
          format: date-time
          description: ISO 8601 timestamp of publication.
        type:
          type: string
          description: Note type (e.g. `feed`).
        user_id:
          type: integer
        publication_id:
          type: integer
          description: Publication ID if posted on behalf of a publication. Null for standalone notes.
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/NoteAttachment'
          description: Attached media.
      additionalProperties: true
    CreateAttachmentRequest:
      type: object
      description: Body for `POST /api/v1/comment/attachment/`.
      properties:
        url:
          type: string
          format: uri
          description: URL of the content to attach.
        type:
          type: string
          enum:
            - link
          description: Attachment type (e.g. `link`).
      required:
        - url
        - type
      additionalProperties: true
    CreateAttachmentResponse:
      type: object
      description: Created attachment record.
      properties:
        id:
          type: string
          description: UUID of the created attachment.
        type:
          type: string
          enum:
            - link
            - post
            - image
          description: 'Type of the resolved attachment content: `link` for URL links, `post` for Substack posts, `image` for images.'
        url:
          type: string
          format: uri
          description: URL of the linked content.
        image_url:
          type: string
          format: uri
        title:
          type: string
          description: Title of the linked content.
        description:
          type: string
          description: Description of the linked content.
        host:
          type: string
          description: Hostname of the linked URL.
      required:
        - id
        - type
      additionalProperties: true
    DraftPost:
      type: object
      description: A draft or published post from the post management endpoints.
      required:
        - id
      properties:
        id:
          type: integer
          description: Unique numeric post/draft ID.
        uuid:
          type: string
          description: Unique identifier in UUID format.
        editor_v2:
          type: boolean
          description: Whether the draft was created with the v2 (TipTap) editor.
        publication_id:
          type: integer
          description: ID of the publication this draft belongs to.
        type:
          type: string
          description: Post type (e.g. `newsletter`).
        post_date:
          type: string
          format: date-time
          description: ISO 8601 timestamp of publication. Null for unpublished drafts.
        draft_created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of draft creation.
        draft_updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of last draft save.
        email_sent_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the email was sent. Null if not yet sent.
        is_published:
          type: boolean
          description: Whether the draft has been published.
        title:
          type: string
          description: Published title of the post.
        draft_title:
          type: string
          description: Title as last saved in the draft editor (may differ from published title).
        draft_body:
          type: string
          description: HTML body content.
        body:
          type: string
          description: Draft body content (HTML).
        subtitle:
          type: string
          description: Published subtitle.
        draft_subtitle:
          type: string
          description: Subtitle as last saved in the draft editor.
        slug:
          type: string
          description: URL slug for the post.
        audience:
          type: string
          description: Audience visibility (e.g. `everyone`, `only_paid`).
        cover_image:
          type: string
          format: uri
        should_send_email:
          type: boolean
          description: Whether to send an email notification on publish.
        should_send_free_preview:
          type: boolean
          description: Whether to send a free preview excerpt to non-subscribers.
        write_comment_permissions:
          type: string
          description: Who can write comments (e.g. `everyone`, `only_paid`).
        default_comment_sort:
          type: string
          description: Default comment sort order (e.g. `best_first`).
        meter_type:
          type: string
          description: Paywall metering strategy. `none` for no metering.
        section_id:
          type: integer
        section_slug:
          type: string
          description: URL slug of the section.
        section_name:
          type: string
          description: Display name of the section.
        draft_section_name:
          type: string
          description: Section name as saved in the draft.
        is_draft_hidden:
          type: boolean
          description: Whether the draft is hidden from the post management list.
        reaction_count:
          type: integer
          description: Number of reactions.
        comment_count:
          type: integer
          description: Number of comments.
        child_comment_count:
          type: integer
          description: Number of nested child comments.
        reactions:
          type: object
          additionalProperties:
            type: integer
          description: Reaction counts keyed by emoji.
        free_unlock_required:
          type: boolean
          description: Whether free users must unlock to read the full post.
        word_count:
          type: integer
          description: Word count of the post body.
      additionalProperties: true
    PostManagementResponse:
      type: object
      description: Paginated response from post management list endpoints.
      required:
        - posts
        - offset
        - limit
        - total
      properties:
        posts:
          type: array
          items:
            $ref: '#/components/schemas/DraftPost'
        offset:
          type: integer
          description: Pagination offset.
        limit:
          type: integer
          description: Page size limit.
        total:
          type: integer
          description: Total number of items matching the query.
    PostManagementCounts:
      type: object
      description: Post counts by status.
      required:
        - published
        - drafts
        - scheduled
      properties:
        published:
          type: integer
          description: Count of published posts.
        drafts:
          type: integer
          description: Count of draft (unpublished) posts.
        scheduled:
          type: integer
          description: Count of scheduled posts.
    PublicationDetail:
      type: object
      description: Publication metadata from GET /publication.
      properties:
        subdomain:
          type: string
          description: Substack subdomain.
        name:
          type: string
          description: Publication display name.
        is_personal_mode:
          type: boolean
          description: Whether the publication is in personal mode (single-author display).
        custom_domain:
          type: string
          description: Custom domain (e.g. `www.example.com`).
        logo_url:
          type: string
          format: uri
          description: URL of the publication logo.
        cover_photo_url:
          type: string
          format: uri
          description: URL of the cover/banner image.
        email_from_name:
          type: string
          description: Display name used in the email From field.
        language:
          type: string
          description: ISO 639-1 language code.
        tags:
          type: array
          items:
            type: object
          description: Publication tags (topics/categories). Each tag has `id` (UUID), `name`, `slug`, and `hidden` fields.
        has_paid_subs:
          type: boolean
          description: Whether the publication has any paid subscribers.
        community_enabled:
          type: boolean
          description: Whether community features are enabled.
        podcast_enabled:
          type: boolean
          description: Whether podcast features are enabled.
        bylines_enabled:
          type: boolean
          description: Whether author bylines are shown on posts.
        invite_only:
          type: boolean
          description: Whether the publication requires an invitation to subscribe.
        founding_plan_enabled:
          type: boolean
          description: Whether the founding member plan is available.
        boost_enabled:
          type: boolean
          description: Whether the boost/recommendation feature is enabled.
        tts_enabled:
          type: boolean
          description: Whether text-to-speech audio generation is enabled.
      additionalProperties: true
    PostTag:
      type: object
      description: A publication post tag.
      properties:
        id:
          type: integer
        name:
          type: string
          description: Tag display name, shown as a hashtag link in the post body.
    Subscription:
      type: object
      description: A user's subscription to a publication.
      required:
        - id
        - user_id
        - publication_id
      properties:
        id:
          type: integer
        user_id:
          type: integer
          description: Subscriber's user ID.
        publication_id:
          type: integer
        email_disabled:
          type: boolean
          description: Whether email delivery is disabled for this subscription.
        membership_state:
          type: string
          description: Subscription tier. `free_signup` for free, `subscribed` for paid.
        type:
          type: string
          description: Legacy/reserved field. Always null in observed responses. Use `membership_state` to determine subscription tier.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of subscription creation.
        visibility:
          type: string
          description: Visibility setting. `public` or `private`.
        is_founding:
          type: boolean
          description: Whether this is a founding member subscription.
        is_favorite:
          type: boolean
          description: Whether the user has favorited this subscription.
    SubscriptionsResponse:
      type: object
      description: Paginated list of subscriptions.
      required:
        - subscriptions
      properties:
        subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/Subscription'
        more:
          type: boolean
          description: Whether more subscriptions are available.
    PublisherSettings:
      type: object
      description: Publisher settings from GET /settings.
      required:
        - settings
      properties:
        settings:
          type: object
          additionalProperties: true
          description: Publisher settings object with key-value configuration.
        twitterAccount:
          type: object
          additionalProperties: true
          description: Connected Twitter/X account details. Null if not linked.
        userInboxView:
          type: object
          additionalProperties: true
          description: User's inbox view preferences.
        hasActiveSubscriptionSection:
          type: boolean
          description: Whether the publication has an active paid subscription section.
      additionalProperties: true
    LiveStreamList:
      type: object
      description: Live stream list response.
      properties:
        liveStreams:
          type: array
          items:
            $ref: '#/components/schemas/LiveStream'
        hasMore:
          type: boolean
          description: Whether more live streams are available.
    DashboardSummary:
      type: object
      description: Aggregate dashboard metrics for a publication.
      properties:
        appSubscribers:
          type: integer
          description: Number of app subscribers.
        subscribers:
          type: integer
          description: Total subscriber count.
        totalEmail:
          type: integer
          description: Total emails sent.
        views:
          type: integer
        openRate:
          type: number
          description: Email open rate (0-1).
        pledgesAmount:
          type: number
          description: Total pledge amount.
        numPledges:
          type: integer
          description: Number of pledges.
        pledgeCurrency:
          type: string
          description: Pledge currency code (e.g. `USD`).
        isBestseller:
          type: boolean
          description: Whether the publication is a bestseller.
      additionalProperties: true
    EmailStatsTimeseries:
      type: object
      description: Time-series email statistics for a publication.
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              date:
                type: string
                format: date
              sends:
                type: integer
              opens:
                type: integer
              clicks:
                type: integer
            additionalProperties: true
          description: Time-series data points for email open/click statistics.
      additionalProperties: true
    UnreadActivity:
      type: object
      description: Unread activity count response.
      properties:
        count:
          type: integer
          description: Number of unread activity items.
      additionalProperties: true
    UnreadMessagesCount:
      type: object
      description: Unread messages count response.
      properties:
        count:
          type: integer
          description: Number of unread messages.
      additionalProperties: true
    PublicationUsersResponse:
      type: object
      description: Response from GET /publication_user.
      properties:
        pub_users:
          type: array
          items:
            $ref: '#/components/schemas/PublicationUser'
          description: Publication-user relationship objects.
      additionalProperties: true
    BoostSettings:
      type: object
      description: Boost settings for a publication.
      properties:
        boost_enabled:
          type: boolean
          description: Whether boost is enabled.
        features:
          type: object
          description: Feature flags for boost.
          additionalProperties: true
      additionalProperties: true
    PublicationSettings:
      type: object
      description: Publication settings and configuration.
      properties:
        id:
          type: integer
          description: Publication ID.
        name:
          type: string
          description: Publication display name.
        subdomain:
          type: string
          description: Substack subdomain.
        custom_domain:
          type: string
        language:
          type: string
          description: ISO 639-1 language code.
        community_enabled:
          type: boolean
          description: Whether community features are enabled.
        podcast_enabled:
          type: boolean
          description: Whether podcast features are enabled.
        bylines_enabled:
          type: boolean
          description: Whether bylines are displayed.
        invite_only:
          type: boolean
          description: Whether invitation is required.
        founding_plan_enabled:
          type: boolean
          description: Whether founding plan is available.
        boost_enabled:
          type: boolean
          description: Whether boost feature is enabled.
        tts_enabled:
          type: boolean
          description: Whether text-to-speech is enabled.
      additionalProperties: true
    PledgePlan:
      type: object
      description: A single payment pledge plan.
      properties:
        name:
          type: string
        amount:
          type: integer
          description: Plan amount in minor currency units (e.g. cents).
        interval:
          type: string
          description: Billing interval (e.g. `month`, `year`).
        currency:
          type: string
          description: Currency code (e.g. `USD`).
      additionalProperties: true
    PledgePlans:
      type: object
      description: Pledge plans for a publication.
      properties:
        enabled:
          type: boolean
          description: Whether pledges are enabled.
        payment_pledge_plans:
          type: array
          items:
            $ref: '#/components/schemas/PledgePlan'
          description: Available pledge/payment plan options.
      additionalProperties: true
    PledgePlansSummary:
      type: object
      description: Summary of pledge plans with aggregate statistics.
      properties:
        plans:
          type: array
          items:
            $ref: '#/components/schemas/PledgePlan'
        pledgeSummary:
          type: object
          description: Aggregate pledge statistics.
          additionalProperties: true
        pledgeCount:
          type: integer
          description: Total number of pledges.
      additionalProperties: true
    BestsellerTier:
      type: object
      description: Publication bestseller tier information.
      properties:
        publication_id:
          type: integer
        tier:
          type: string
          description: Bestseller tier level (e.g. `top_10`).
        category:
          type: string
          description: Bestseller category name (e.g. `technology`).
        rank:
          type: integer
          description: Current rank within category.
        updated_at:
          type: string
          format: date-time
          description: When the tier was last updated.
      additionalProperties: true
    RecommendationItem:
      type: object
      description: A single recommendation entry.
      properties:
        id:
          type: integer
          description: Recommendation ID.
        recommender_publication_id:
          type: integer
          description: Publication making the recommendation.
        recommended_publication_id:
          type: integer
          description: Publication being recommended.
        recommended_publication:
          type: object
          properties:
            id:
              type: integer
            name:
              type: string
            subdomain:
              type: string
            logo_url:
              type: string
              format: uri
            description:
              type: string
          additionalProperties: true
          description: The recommended publication object.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the recommendation was created.
      additionalProperties: true
    RecommendationsResponse:
      type: object
      description: List of recommendations or suggested recommendations.
      properties:
        recommendations:
          type: array
          items:
            $ref: '#/components/schemas/RecommendationItem'
        next_cursor:
          oneOf:
            - type: string
            - type: 'null'
          description: Opaque pagination cursor. Null if no more pages.
        more:
          type: boolean
          description: Whether more recommendations are available.
      additionalProperties: true
    RecommendationStats:
      type: object
      description: Recommendation statistics (incoming or outgoing).
      properties:
        total:
          type: integer
          description: Total number of recommendations.
        publications:
          type: array
          items:
            type: object
            properties:
              publication_id:
                type: integer
              name:
                type: string
              count:
                type: integer
            additionalProperties: true
          description: Publication recommendation statistics.
      additionalProperties: true
    RecommendationsExist:
      type: object
      description: Check result for whether recommendations exist.
      properties:
        exist:
          type: boolean
          description: Whether any recommendations are configured.
      additionalProperties: true
    NoteStats:
      type: object
      description: |
        Analytics stats for a single note, returned as an array of cards.
        Each card represents a metric category displayed in the Substack UI.
      properties:
        cards:
          type: array
          description: Metric cards for the note.
          items:
            $ref: '#/components/schemas/NoteStatsCard'
      additionalProperties: true
    NoteStatsCard:
      type: object
      description: A single analytics metric card for a note.
      properties:
        title:
          type: string
          description: Card title (e.g. "Impressions", "Surfaces", "Audience", "Interactions").
        value:
          type: integer
          description: Primary numeric value for the metric.
        cards:
          type: array
          description: Nested sub-cards (e.g. breakdown of impression sources).
          items:
            $ref: '#/components/schemas/NoteStatsCard'
        timeseries:
          type: array
          description: Time-series data points (for charts like impressions over time).
          items:
            type: object
            properties:
              date:
                type: string
                format: date
              value:
                type: integer
            additionalProperties: true
      additionalProperties: true
