Skip to guide
Subnano Docs First API request

Discussion, follows and notifications

Every v1 endpoint on this page requires a verified owner Bearer session and uses private, no-store. Publishing keys cannot act as readers. Existing website cookie routes remain available. Errors use the H3 JSON family, not the Publishing problem format.

Comments, replies and reports

Method and pathRequest/result
GET /api/v1/posts/:postId/comments{comments,pagination:{limit,nextCursor,hasMore}}
POST /api/v1/posts/:postId/comments{content,parentCommentId?,replyTargetCommentId?} → saved Comment + replayed
GET /api/v1/comments/:commentIdVisible saved Comment with author/reaction/tip metadata
DELETE /api/v1/comments/:commentId{success,id,post_id,deleted_at,deleted_by_role}
POST /api/v1/posts/:postId/reports{reason} → {success,reportId,status,replayed}
POST /api/v1/comments/:commentId/reportsSame report contract

Append Comment/report calls require Idempotency-Key: 1–128 visible ASCII characters. Persist it with the exact normalized intent. Replaying returns the saved ID/result without another insert or mutation-rate charge; changed intent returns 409 data.code: "idempotency_key_conflict". A separate duplicate report returns 409 duplicate_report. A created-comment reload failure can include data.commentId; retry the same append key to recover.

Comment content and report reason trim to 1–2000 characters. A reply's parent and target must belong to the same Post; a target requires a parent and otherwise defaults to the parent. Existing reply-depth, account setup, ban/shadow moderation, published visibility, disabled-comment and database rate rules still apply. Deletion is a tombstone for your own Comment or moderation on your own Post; it preserves threads and payment evidence. Ordinary API authority does not include admin adjudication.

Comment pagination accepts limit 1–100 (default 50), optional threadRootId UUID and opaque cursor. Copy the returned cursor with the same Post/thread filter; continuation uses stable created_at,id. The complete Comment field set is in the OpenAPI schema, including nullable deletion fields, author identity/kind, purchase flag, reaction counts and separate native tip raw amounts.

Reactions

GET and PUT /api/v1/posts/:postId/reactions and /api/v1/comments/:commentId/reactions share the same owner rule. PUT accepts {reaction:"thumbs_up"|"heart"|"fire"|"clap"|null}; null clears the desired state. Own-target reactions and inaccessible/removed targets remain restricted. Success returns {summary:{counts,total,viewerReaction}}.

GET returns {summary,reaction,reactors,pagination}. Without reaction the reactors are empty and pagination null. With a supported reaction, limit defaults to 40 (max 100), offset defaults to zero; each reactor exposes public Profile identity and reacted time, and pagination is {limit,offset,total,hasMore}. Never use a read failure as proof of a zero count.

Follow and per-follow alerts

GET /api/v1/follows/:creatorId returns {following,follow} for your own relationship. PUT with {following:true|false} sets the desired state without duplicates and returns the saved relationship; self-follow returns 400. Save follow.id for alerts.

GET /api/v1/follows/preferences/:followId reads owned preferences. PUT accepts a nonempty partial object of boolean notify_new_posts, notify_channel_email, notify_channel_inapp. Omitted fields remain saved; unknown/nonboolean keys return 400, foreign follow returns 404.

Existing public GET /api/followers/:userId, /api/following/:userId use page (default 1), per_page (default 10) and return {data,total,page,per_page}. GET /api/follows/counts/:profileId returns follow counts. Reuse these public routes rather than a new catalogue.

Notification and privacy settings

GET /api/v1/notification-preferences returns the saved preference row. PUT accepts a nonempty partial boolean object; it never replaces omitted settings or accepts an owner ID.

Supported keys: email_notifications, in_app_notifications, push_notifications; notify_new_purchase, notify_new_post, notify_new_follow, notify_account_issues, notify_summaries, notify_new_comment, notify_comment_reply, notify_commented_post_activity, and each corresponding _inapp key; show_on_creator_leaderboards, show_on_supporter_leaderboard. Setting push preference cannot approve a browser/OS permission dialog.

Poll and mark notifications

GET /api/v1/notifications?limit=20&offset=0&includeAll=false returns {notifications,total,unread,preferences,pagination,includeAll}. Limit clamps to 1–50; offset is nonnegative. The default feed includes unread enabled in-app types; includeAll=true also includes read records. Disabling in-app notifications returns an empty feed. Rows are ordered by created_at,id descending. Offset is not a snapshot cursor: deduplicate by id when new notifications shift pages.

  • POST /api/v1/notifications/mark-read with {ids:[<UUID>,...]} returns {updated}. Duplicates are collapsed; empty IDs update zero. Only your own rows can change.
  • POST /api/v1/notifications/mark-context-read with {context:{postId?,purchaseId?,commentId?,followerId?,types?}} marks owned matching context. Snake-case ID equivalents also work; require at least one usable filter, unknown keys fail 400.
  • POST /api/v1/notifications/mark-all-read marks only your owned notifications.
  • GET /api/v1/notifications/posts/:postId/mute returns {muted}. PUT {muted:true|false} saves that discussion preference; it does not unmute/alter global channels.

Read markers are safe desired-state retries. Polling/mutations retain server limits; respect 429 and available Retry-After. Private feeds, payloads and contextual IDs must not enter shared caches.

Public discussion and discovery

Public reads need no Publishing key. Supply an optional verified owner Bearer only for viewer reaction/purchase/moderation context. Invalid explicit Authorization fails; it never falls back to cookies.

Existing canonical GETResponse and continuation
/api/posts/:postId/commentsNo paging query: Comment[]. Presence of limit, cursor or threadRootId: {comments,pagination:{limit,nextCursor,hasMore}}; limit 1–100/default50; ascending creation time/ID; cursor bound to Post/thread. Hidden/disabled/missing discussion is empty.
/api/posts/:postId/reactions, /comments/:commentId/reactions{summary,reaction,reactors,pagination}. With no/blank reaction: reactors empty, reaction/pagination null. Selected enum gives numeric limit(default40/max100)/offset continuation.
/api/posts/:postId/tip-activitiesNewest visible public activity array; exact amountReceivedRaw strings; no pagination. Missing/withdrawn Post returns404 publicly; its owner retains permitted context.
/api/posts/:postId/comments/:commentId/tips/supportersVisible nondeleted Comment only; array of direct paid Comment-author supporters grouped by tipper, exact positive tipAmountRaw or null; no pagination.
/api/posts/:postId/related{data} published summaries, limit default4/max12; no protected content; cache300seconds plus stale window.
/api/leaderboardsPublic rows plus optional own my_rank; board enum, limit default25/max100, offset up to2147483647. Values are numeric analytics.
/api/profile/handle-availability?handle=...{available} hint without reservation or full registration validation.

Reaction/supporter reads allow40 requests/10seconds/user-or-IP. Their429 supplies data.retryAfter seconds, without a universal Retry-After header. Anonymous tip identities retain native UUIDs while name/handle/avatar presentation is masked. Creator leaderboard privacy filters live; supporter public-row removal follows the five-minute materialized refresh, while the viewer’s hidden state/rank updates immediately.

Private reader mutations use the v1 owner routes above. Public activity is not a private accounting receipt. OpenAPI preserves exact DTO casing/nullability and canonical response variants.