# Comments ## List comments **get** `/v2/comments` Returns comments for your organization. Comments are threaded discussions. Each comment can have: - Author information - Voting (upvotes/downvotes) - Privacy settings (public/private) - Moderation status - Parent comment reference for threading ### Filtering Optionally filter by: - `postId` - Get comments for a specific post - `changelogId` - Get comments for a specific changelog If no filter is provided, returns all comments across the organization. ### Pagination This endpoint uses **cursor-based pagination**: - `limit` - Number of comments to return (1-100, default 10) - `cursor` - Opaque cursor from a previous response's `nextCursor` field **Example:** To paginate through results: 1. First request: `GET /v2/comments?postId={id}&limit=10` 1. If `nextCursor` is not null, use it for the next page 1. Next request: `GET /v2/comments?postId={id}&limit=10&cursor={nextCursor}` ### Response Format Returns a list object with: - `object` - Always "list" - `data` - Array of comment objects (flat structure with `parentCommentId` for threading) - `nextCursor` - Cursor for the next page (null if no more results) ### Comment Structure Each comment includes: - `id` - Unique comment identifier - `postId` / `changelogId` - Reference to the parent content - `parentCommentId` - Reference to parent comment (null for root comments) - `content` - Comment content in HTML format - `author` - Author information (id, name, profilePicture, type) - `upvotes` / `downvotes` / `score` - Voting metrics - `isPrivate` - Whether comment is only visible to admins - `inReview` - Whether comment is pending moderation - `created` / `updated` - Unix timestamps ### Additional Filters - `privacy` - Filter by privacy: "public", "private", or "all" - `inReview` - Filter by moderation status (true/false) ### Sorting Use `sortBy` to sort results: - `best` - Sort by confidence score (default, like Reddit) - `top` - Sort by net score (upvotes - downvotes) - `new` - Sort by creation date, newest first - `old` - Sort by creation date, oldest first ### Query Parameters - `changelogId: optional string` Filter comments by changelog ID - `cursor: optional string` Cursor for pagination. Use nextCursor from previous response. - `inReview: optional boolean` Filter by review status - `limit: optional number` Maximum number of comments to return - `postId: optional string` Filter comments by post ID - `privacy: optional "public" or "private" or "all"` Filter comments by privacy - `"public"` - `"private"` - `"all"` - `sortBy: optional "best" or "top" or "new" or "old"` Sort order for comments - `"best"` - `"top"` - `"new"` - `"old"` ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Returns - `data: array of Comment` Array of comments - `id: string` Unique identifier - `author: object { id, name, profilePicture, type }` - `id: string` Author unique identifier - `name: string` Author display name - `profilePicture: string` Author profile picture URL - `type: "admin" or "customer" or "guest" or 3 more` Type of user who authored the comment - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `changelogId: string` Changelog ID this comment belongs to - `content: string` Comment content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `downvotes: number` Number of downvotes - `inReview: boolean` Whether the comment is in review - `isDeleted: boolean` Whether the comment is deleted - `isPinned: boolean` Whether the comment is pinned - `isPrivate: boolean` Whether the comment is private - `isSpam: boolean` Whether the comment is spam - `object: "comment"` Object type identifier - `"comment"` - `parentCommentId: string` Parent comment ID for replies, null for root comments - `postId: string` Post ID this comment belongs to - `score: number` Net score (upvotes - downvotes) - `updatedAt: string` ISO 8601 timestamp when updated - `upvotes: number` Number of upvotes - `nextCursor: string` Cursor for fetching the next page (cursor-based pagination) - `object: "list"` Object type identifier - `"list"` - `pagination: optional object { limit, page, total, totalPages }` Pagination metadata for page-based requests - `limit: number` Items per page - `page: number` Current page number - `total: number` Total number of items - `totalPages: number` Total number of pages ### Example ```http curl https://do.featurebase.app/v2/comments \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "data": [ { "id": "507f1f77bcf86cd799439011", "author": { "id": "507f1f77bcf86cd799439011", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "changelogId": "507f1f77bcf86cd799439013", "content": "

This is a great idea!

", "createdAt": "2023-12-12T00:00:00.000Z", "downvotes": 0, "inReview": false, "isDeleted": false, "isPinned": false, "isPrivate": false, "isSpam": false, "object": "comment", "parentCommentId": "507f1f77bcf86cd799439014", "postId": "507f1f77bcf86cd799439012", "score": 5, "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 5 } ], "nextCursor": "eyJpZCI6IjUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMSJ9", "object": "list", "pagination": { "limit": 10, "page": 1, "total": 42, "totalPages": 5 } } ``` ## Create a new comment **post** `/v2/comments` Creates a new comment or reply to an existing comment. You can create a comment for a post or changelog. Comments support: - HTML content (images are automatically uploaded to our storage) - Threading (replies via `parentCommentId`) - Privacy controls (private comments visible only to admins) - Author attribution (post on behalf of users) ### Required Fields - `content` - Comment content in HTML format - `postId` OR `changelogId` - One is required to specify the target ### Optional Fields - `parentCommentId` - Create a reply to an existing comment - `isPrivate` - Make comment visible only to admins (default: false) - `sendNotification` - Notify voters about the comment (default: true) - `author` - Post comment under a specific user (uses authenticated user if not provided) - `createdAt` - Backdate creation (useful for imports) ### Author Object The `author` field supports multiple identification methods: - `id` - Featurebase user ID (direct reference) - `userId` - External user ID from your system (via SSO) - `email` - Email address (finds existing or creates new user) - `name` - Display name (used with email for new users) - `profilePicture` - Profile picture URL If no author is provided, the comment is posted under the authenticated user. ### Content Format Content should be formatted as HTML. For images: - External URLs in `img src` attributes are automatically pulled into our storage - Base64 encoded data URIs (`data:image/...`) are also supported and processed ### Response Returns the created comment object with all fields populated, including: - `id` - Unique comment identifier - `author` - Author information - Voting stats and timestamps ### Errors - `400` - Invalid input (missing required fields, invalid IDs) - `403` - Commenting disabled or not authorized - `404` - Post/changelog not found or parent comment not found ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Body Parameters - `content: string` Comment content in HTML format - `author: optional AuthorInput` Author to attribute the post to. If not provided, uses the authenticated user. Supports multiple identification methods: id (Featurebase ID), userId (external SSO ID), or email. - `id: optional string` Featurebase user ID to attribute content to - `email: optional string` Author email (used to find or create user) - `name: optional string` Author display name - `profilePicture: optional string` Author profile picture URL - `userId: optional string` External user ID from your system (matched via SSO) - `changelogId: optional string` Changelog ID to comment on (accepts ObjectId or slug) - `createdAt: optional string` Set the date when the comment was created. Useful for importing comments from other platforms. - `downvotes: optional number` Initial downvotes count. Useful for importing comments from other platforms. Score will be calculated as upvotes - downvotes. - `isPrivate: optional boolean` Whether the comment is private (only visible to admins) - `parentCommentId: optional string` Parent comment ID if this is a reply - `postId: optional string` Post ID to comment on (accepts ObjectId or slug) - `sendNotification: optional boolean` Whether to notify voters of the submission about the comment - `upvotes: optional number` Initial upvotes count. Useful for importing comments from other platforms. Score will be calculated as upvotes - downvotes. ### Returns - `Comment object { id, author, changelogId, 14 more }` - `id: string` Unique identifier - `author: object { id, name, profilePicture, type }` - `id: string` Author unique identifier - `name: string` Author display name - `profilePicture: string` Author profile picture URL - `type: "admin" or "customer" or "guest" or 3 more` Type of user who authored the comment - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `changelogId: string` Changelog ID this comment belongs to - `content: string` Comment content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `downvotes: number` Number of downvotes - `inReview: boolean` Whether the comment is in review - `isDeleted: boolean` Whether the comment is deleted - `isPinned: boolean` Whether the comment is pinned - `isPrivate: boolean` Whether the comment is private - `isSpam: boolean` Whether the comment is spam - `object: "comment"` Object type identifier - `"comment"` - `parentCommentId: string` Parent comment ID for replies, null for root comments - `postId: string` Post ID this comment belongs to - `score: number` Net score (upvotes - downvotes) - `updatedAt: string` ISO 8601 timestamp when updated - `upvotes: number` Number of upvotes ### Example ```http curl https://do.featurebase.app/v2/comments \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" \ -d '{ "content": "

This is a great idea!

", "changelogId": "507f1f77bcf86cd799439012", "createdAt": "2025-01-15T10:30:00.000Z", "parentCommentId": "507f1f77bcf86cd799439013", "postId": "507f1f77bcf86cd799439011", "sendNotification": true, "upvotes": 5 }' ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "author": { "id": "507f1f77bcf86cd799439011", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "changelogId": "507f1f77bcf86cd799439013", "content": "

This is a great idea!

", "createdAt": "2023-12-12T00:00:00.000Z", "downvotes": 0, "inReview": false, "isDeleted": false, "isPinned": false, "isPrivate": false, "isSpam": false, "object": "comment", "parentCommentId": "507f1f77bcf86cd799439014", "postId": "507f1f77bcf86cd799439012", "score": 5, "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 5 } ``` ## Get a comment by ID **get** `/v2/comments/{id}` Retrieves a single comment by its unique identifier. Returns the full comment object including: - Author information - Voting stats (upvotes, downvotes, score) - Privacy and moderation status - Threading information (parentCommentId) - Timestamps ### Response Returns a comment object with all fields populated. ### Errors - `400` - Invalid comment ID format - `404` - Comment not found or doesn't belong to your organization ### Path Parameters - `id: string` Comment unique identifier ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Returns - `Comment object { id, author, changelogId, 14 more }` - `id: string` Unique identifier - `author: object { id, name, profilePicture, type }` - `id: string` Author unique identifier - `name: string` Author display name - `profilePicture: string` Author profile picture URL - `type: "admin" or "customer" or "guest" or 3 more` Type of user who authored the comment - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `changelogId: string` Changelog ID this comment belongs to - `content: string` Comment content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `downvotes: number` Number of downvotes - `inReview: boolean` Whether the comment is in review - `isDeleted: boolean` Whether the comment is deleted - `isPinned: boolean` Whether the comment is pinned - `isPrivate: boolean` Whether the comment is private - `isSpam: boolean` Whether the comment is spam - `object: "comment"` Object type identifier - `"comment"` - `parentCommentId: string` Parent comment ID for replies, null for root comments - `postId: string` Post ID this comment belongs to - `score: number` Net score (upvotes - downvotes) - `updatedAt: string` ISO 8601 timestamp when updated - `upvotes: number` Number of upvotes ### Example ```http curl https://do.featurebase.app/v2/comments/$ID \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "author": { "id": "507f1f77bcf86cd799439011", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "changelogId": "507f1f77bcf86cd799439013", "content": "

This is a great idea!

", "createdAt": "2023-12-12T00:00:00.000Z", "downvotes": 0, "inReview": false, "isDeleted": false, "isPinned": false, "isPrivate": false, "isSpam": false, "object": "comment", "parentCommentId": "507f1f77bcf86cd799439014", "postId": "507f1f77bcf86cd799439012", "score": 5, "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 5 } ``` ## Update a comment **patch** `/v2/comments/{id}` Updates an existing comment by its unique identifier. You can update: - **content** - Comment text (HTML format) - **isPrivate** - Privacy status (admin-only visibility) - **isPinned** - Pinned status (displayed at top) - **inReview** - Moderation status ### Content Format Content should be formatted as HTML. For images: - External URLs in `img src` attributes are automatically pulled into our storage - Base64 encoded data URIs (`data:image/...`) are also supported and processed ### Permissions - Comment authors can update their own comment content - Admin permissions required for: - `isPrivate` - Requires `manage_comments_private` permission - `isPinned` - Requires `set_comment_pinned` permission - `inReview` - Requires `moderate_comments` permission - Updating other users' comments - Requires `moderate_comments` permission ### Response Returns the updated comment object with all fields populated. ### Errors - `400` - Invalid comment ID format or input - `403` - Not authorized to update this comment - `404` - Comment not found ### Path Parameters - `id: string` Comment unique identifier ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Body Parameters - `content: optional string` Comment content in HTML format - `createdAt: optional string` Update the creation date (useful for imports) - `downvotes: optional number` Set the downvotes count directly. Score will be recalculated as upvotes - downvotes. - `inReview: optional boolean` Whether the comment is pending moderation review - `isPinned: optional boolean` Whether the comment is pinned at the top - `isPrivate: optional boolean` Whether the comment is private (only visible to admins) - `upvotes: optional number` Set the upvotes count directly. Score will be recalculated as upvotes - downvotes. ### Returns - `Comment object { id, author, changelogId, 14 more }` - `id: string` Unique identifier - `author: object { id, name, profilePicture, type }` - `id: string` Author unique identifier - `name: string` Author display name - `profilePicture: string` Author profile picture URL - `type: "admin" or "customer" or "guest" or 3 more` Type of user who authored the comment - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `changelogId: string` Changelog ID this comment belongs to - `content: string` Comment content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `downvotes: number` Number of downvotes - `inReview: boolean` Whether the comment is in review - `isDeleted: boolean` Whether the comment is deleted - `isPinned: boolean` Whether the comment is pinned - `isPrivate: boolean` Whether the comment is private - `isSpam: boolean` Whether the comment is spam - `object: "comment"` Object type identifier - `"comment"` - `parentCommentId: string` Parent comment ID for replies, null for root comments - `postId: string` Post ID this comment belongs to - `score: number` Net score (upvotes - downvotes) - `updatedAt: string` ISO 8601 timestamp when updated - `upvotes: number` Number of upvotes ### Example ```http curl https://do.featurebase.app/v2/comments/$ID \ -X PATCH \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" \ -d '{ "content": "

This is my updated comment.

", "createdAt": "2025-01-15T10:30:00.000Z", "downvotes": 2, "isPinned": true, "upvotes": 10 }' ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "author": { "id": "507f1f77bcf86cd799439011", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "changelogId": "507f1f77bcf86cd799439013", "content": "

This is a great idea!

", "createdAt": "2023-12-12T00:00:00.000Z", "downvotes": 0, "inReview": false, "isDeleted": false, "isPinned": false, "isPrivate": false, "isSpam": false, "object": "comment", "parentCommentId": "507f1f77bcf86cd799439014", "postId": "507f1f77bcf86cd799439012", "score": 5, "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 5 } ``` ## Delete a comment **delete** `/v2/comments/{id}` Deletes a comment by its unique identifier. ### Deletion Behavior - **Comments with replies**: Soft delete - Content is replaced with "[deleted]" - Author information is anonymized - Comment remains visible to maintain conversation context - Votes and scores are reset to 0 - **Comments without replies**: Hard delete - Comment is permanently removed from the database - All associated data is deleted ### Permissions - Comment authors can delete their own comments - Admins can delete any comment (subject to permissions) - Lite seat admins can only delete their own comments - Non-authors require `manage_comments` or `manage_comments_private` permission ### Response Returns a deletion confirmation: ```json { "id": "507f1f77bcf86cd799439011", "object": "comment", "deleted": true } ``` ### Errors - `400` - Invalid comment ID format - `403` - Not authorized to delete this comment - `404` - Comment not found or doesn't belong to your organization ### Path Parameters - `id: string` Comment unique identifier ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Returns - `id: string` Unique identifier of the deleted comment - `deleted: true` Indicates the resource was deleted - `true` - `object: "comment"` Object type identifier - `"comment"` ### Example ```http curl https://do.featurebase.app/v2/comments/$ID \ -X DELETE \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "deleted": true, "object": "comment" } ``` ## Delete a comment **delete** `/v2/comment` Deletes a comment using the legacy Clover API format. This endpoint accepts the comment ID in the request body instead of the route parameter. ### Request Body ```json { "id": "507f1f77bcf86cd799439011" } ``` ### Deletion Behavior - **Comments with replies**: Soft delete - Content is replaced with "[deleted]" - Author information is anonymized - Comment remains visible to maintain conversation context - Votes and scores are reset to 0 - **Comments without replies**: Hard delete - Comment is permanently removed from the database - All associated data is deleted ### Permissions - Comment authors can delete their own comments - Admins can delete any comment (subject to permissions) - Lite seat admins can only delete their own comments - Non-authors require `manage_comments` or `manage_comments_private` permission ### Response Returns a success confirmation (Clover format): ```json { "success": true } ``` Note: Nova API returns `{ id, object: "comment", deleted: true }`, but Clover transformer converts it to `{ success: true }` for backwards compatibility. ### Errors - `400` - Invalid comment ID format or missing ID in body - `403` - Not authorized to delete this comment - `404` - Comment not found or doesn't belong to your organization ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Body Parameters - `id: string` Comment ID ### Returns - `success: boolean` ### Example ```http curl https://do.featurebase.app/v2/comment \ -X DELETE \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "success": true } ``` ## Domain Types ### Comment - `Comment object { id, author, changelogId, 14 more }` - `id: string` Unique identifier - `author: object { id, name, profilePicture, type }` - `id: string` Author unique identifier - `name: string` Author display name - `profilePicture: string` Author profile picture URL - `type: "admin" or "customer" or "guest" or 3 more` Type of user who authored the comment - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `changelogId: string` Changelog ID this comment belongs to - `content: string` Comment content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `downvotes: number` Number of downvotes - `inReview: boolean` Whether the comment is in review - `isDeleted: boolean` Whether the comment is deleted - `isPinned: boolean` Whether the comment is pinned - `isPrivate: boolean` Whether the comment is private - `isSpam: boolean` Whether the comment is spam - `object: "comment"` Object type identifier - `"comment"` - `parentCommentId: string` Parent comment ID for replies, null for root comments - `postId: string` Post ID this comment belongs to - `score: number` Net score (upvotes - downvotes) - `updatedAt: string` ISO 8601 timestamp when updated - `upvotes: number` Number of upvotes ### Comment Delete 0 Response - `CommentDelete0Response object { id, deleted, object }` - `id: string` Unique identifier of the deleted comment - `deleted: true` Indicates the resource was deleted - `true` - `object: "comment"` Object type identifier - `"comment"` ### Comment Delete 1 Response - `CommentDelete1Response object { success }` - `success: boolean`