The REST API is mounted under /api/v1 and assembled from gateway_oss's health
routes plus each domain's _rest capability (see CLAUDE.md for the trio
layout). It totals 45 endpoints across 33 paths.
Every route below requires the x-gateway-token header except
GET /api/v1/health/live. This includes read-only, "public-looking" routes
such as GET /api/v1/profiles/{slug} — the header is used to build the
per-request Substack HTTP clients even when the underlying Substack data is
public, so a request without it gets 422, not a successful anonymous read.
See Authentication for the token shape.
Public liveness probe. No credentials required. Returns {"status": "ok"}.
Authenticated readiness probe that calls Substack to verify connectivity.
Optional query parameter show (bool, default false): when true, echoes
the decoded credential payload back in the response.
Returns the authenticated user's own profile.
Returns a page of the authenticated user's own notes. Query parameter:
cursor (opaque, optional).
Returns a page of the authenticated user's own posts. Query parameters:
limit (default 25, max 100), cursor (opaque, optional).
Returns the list of profiles the authenticated user follows.
Returns a single note by numeric ID.
Converts Markdown content to a Substack note and publishes it. Body:
content (string, required), attachment (URL, optional). Returns
201 Created with the new note's id.
Deletes a note by numeric ID. Returns 204 No Content.
Adds a like to a note. Returns 204 No Content.
Removes a like from a note. Returns 204 No Content.
Replies to a note, or to any comment inside its thread (note_id may name
either). Body: body (string, required). Returns 201 Created.
Lists direct replies to a note (or any node in its thread).
Returns a single post with its full content (HTML converted to Markdown).
Adds a like to a post. Returns 204 No Content.
Removes a like from a post. Returns 204 No Content.
Restacks a post. Returns 204 No Content.
Returns a public profile by handle slug.
Returns a page of posts for the profile. Query parameters: limit (default
25, max 100), cursor (opaque, optional).
Returns a page of notes for the profile. Query parameter: cursor (opaque,
optional).
Creates a top-level comment on a post. Body: body (string, required).
Returns 201 Created.
Replies to an existing comment. Body: body (string, required). Returns
201 Created, or 404 if the parent comment does not exist.
Returns a single comment by ID (the rich comment shape).
Deletes a comment. Returns 204 No Content.
Lists replies to a comment.
Likes a comment. Returns 204 No Content.
Removes a like from a comment. Returns 204 No Content.
Returns the comments for a post.
Lists post drafts, newest first, in pages of 10. Query parameter: next
(opaque cursor from a previous response). Returns 400 on an invalid
cursor.
Fetches a single draft by ID.
Updates specific fields of a draft. Body: any of title, subtitle, body
— only the fields actually set in the request are applied.
Deletes a draft. Returns 204 No Content.
Creates a new draft. Body: title, subtitle, body (all optional).
Returns 201 Created.
Runs Substack's Pangram AI-writing detection on a draft.
Runs Substack's pre-publish validation on a draft.
Schedules a draft for timed release. Body: scheduled_at (datetime,
required), post_audience and email_audience (optional, default
"only_paid").
Cancels a draft's scheduled release. Returns 204 No Content.
Uploads an image file (multipart file) to Substack and returns its hosted
URL and dimensions. Returns 201 Created.
Daily subscriber counts (paid / comps / free trials / total) as a
timeseries. Query parameter: from (ISO timestamp, defaults to the last 7
days). If a cache extension is installed, only the tail beyond the last
stored day is re-fetched from Substack on subsequent calls; OSS ships no such
cache, so every call fetches the full window.
Trailing-30-day publication views and their delta versus the prior period.
Likes, comment summary, and commenters for a post.
Referrers, devices, and category breakdown of a post's views.
Per-recipient email delivery / open / click rows. Query parameters: limit
(default 20, max 100), offset (default 0).
Subscriber growth attributed to a post.
Comment/discussion thread for a post. Query parameter: cursor (opaque
discussion cursor, optional).
400: invalid request payload (e.g. invalid note content, invalid cursor)401: invalid or malformedx-gateway-token, or Substack rejected the credentials404: upstream not found, passed through where applicable422: missingx-gateway-tokenheader, or request validation failure429: upstream rate limiting, passed through where applicable502: upstream API failure that doesn't map to one of the above