# Feedback # Boards ## List all boards **get** `/v2/boards` Returns all boards (post categories) for the authenticated organization. Boards are containers for posts/feedback. Each board can have different: - Access controls (public, private, segment-restricted) - Feature toggles (comments, posting enabled) - Custom fields This endpoint returns all boards without pagination. Organizations typically have a small number of boards. ### 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 - `access: object { adminOnly, allowedRoles, deniedRoles, segments }` - `adminOnly: boolean` If true, only admins can see this board - `allowedRoles: array of string` Role IDs allowed access (empty = all) - `deniedRoles: array of string` Role IDs explicitly denied access - `segments: array of string` Segment IDs that can access (empty = all) - `createdAt: string` ISO 8601 timestamp when created - `customFields: array of string` Custom field IDs attached to this board - `features: object { commentsEnabled, createdDatesVisible, postingEnabled }` - `commentsEnabled: boolean` Whether users can comment on posts - `createdDatesVisible: boolean` Whether creation dates are visible on posts - `postingEnabled: boolean` Whether users can create new posts - `icon: object { type, value } or object { type, value } or object { type, value }` The board's icon. Can be one of three types: - **emoji**: A single emoji character (e.g., 💡, 🚀, ⭐) - **icon**: A predefined icon from the built-in library (e.g., lightbulb, bug, star) - **url**: A custom image URL (HTTPS required) Can be `null` if no icon is set. - `EmojiIcon object { type, value }` An emoji character as the board icon - `type: "emoji"` Emoji icon type - `"emoji"` - `value: string` A single emoji character - `PredefinedIcon object { type, value }` A predefined icon from the built-in icon library - `type: "icon"` Predefined icon type - `"icon"` - `value: string` Icon name from the predefined icon set (e.g., lightbulb, bug, star, rocket, flag, heart, check, question, megaphone, gift) - `URLIcon object { type, value }` A custom icon loaded from an external URL - `type: "url"` External URL icon type - `"url"` - `value: string` HTTPS URL to a custom icon image (PNG, SVG, or WebP recommended) - `localization: object { description, formPlaceholder, heroDescription, 3 more }` - `description: map[string]` Localized description - `formPlaceholder: map[string]` Placeholder text in the post creation form - `heroDescription: map[string]` Hero description/subtitle - `heroTitle: map[string]` Hero title shown on the board page - `name: map[string]` Localized board name (language code → text) - `submitButtonText: map[string]` Submit button text - `name: string` Display name in organization's default locale - `object: "board"` Object type identifier - `"board"` - `postDefaults: object { visibility }` - `visibility: "public" or "authorOnly" or "companyOnly"` Default visibility for new posts - `"public"` - `"authorOnly"` - `"companyOnly"` ### Example ```http curl https://do.featurebase.app/v2/boards \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json [ { "id": "507f1f77bcf86cd799439011", "access": { "adminOnly": false, "allowedRoles": [ "string" ], "deniedRoles": [ "role_blocked" ], "segments": [ "string" ] }, "createdAt": "2023-12-12T00:00:00.000Z", "customFields": [ "cf_priority", "cf_category" ], "features": { "commentsEnabled": true, "createdDatesVisible": true, "postingEnabled": true }, "icon": { "type": "emoji", "value": "💡" }, "localization": { "description": { "en": "Submit and vote on feature ideas" }, "formPlaceholder": { "en": "Describe your feature idea..." }, "heroDescription": { "foo": "string" }, "heroTitle": { "en": "Share your ideas" }, "name": { "en": "Feature Requests", "es": "Solicitudes de funciones" }, "submitButtonText": { "en": "Submit Feedback" } }, "name": "Feature Requests", "object": "board", "postDefaults": { "visibility": "public" } } ] ``` ## Get a board by ID **get** `/v2/boards/{id}` Retrieves a single board by its unique identifier. Returns the full board object including all access controls, feature toggles, and localization settings. ### Path Parameters - `id: string` Board 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 - `Board object { id, access, createdAt, 7 more }` - `id: string` Unique identifier - `access: object { adminOnly, allowedRoles, deniedRoles, segments }` - `adminOnly: boolean` If true, only admins can see this board - `allowedRoles: array of string` Role IDs allowed access (empty = all) - `deniedRoles: array of string` Role IDs explicitly denied access - `segments: array of string` Segment IDs that can access (empty = all) - `createdAt: string` ISO 8601 timestamp when created - `customFields: array of string` Custom field IDs attached to this board - `features: object { commentsEnabled, createdDatesVisible, postingEnabled }` - `commentsEnabled: boolean` Whether users can comment on posts - `createdDatesVisible: boolean` Whether creation dates are visible on posts - `postingEnabled: boolean` Whether users can create new posts - `icon: object { type, value } or object { type, value } or object { type, value }` The board's icon. Can be one of three types: - **emoji**: A single emoji character (e.g., 💡, 🚀, ⭐) - **icon**: A predefined icon from the built-in library (e.g., lightbulb, bug, star) - **url**: A custom image URL (HTTPS required) Can be `null` if no icon is set. - `EmojiIcon object { type, value }` An emoji character as the board icon - `type: "emoji"` Emoji icon type - `"emoji"` - `value: string` A single emoji character - `PredefinedIcon object { type, value }` A predefined icon from the built-in icon library - `type: "icon"` Predefined icon type - `"icon"` - `value: string` Icon name from the predefined icon set (e.g., lightbulb, bug, star, rocket, flag, heart, check, question, megaphone, gift) - `URLIcon object { type, value }` A custom icon loaded from an external URL - `type: "url"` External URL icon type - `"url"` - `value: string` HTTPS URL to a custom icon image (PNG, SVG, or WebP recommended) - `localization: object { description, formPlaceholder, heroDescription, 3 more }` - `description: map[string]` Localized description - `formPlaceholder: map[string]` Placeholder text in the post creation form - `heroDescription: map[string]` Hero description/subtitle - `heroTitle: map[string]` Hero title shown on the board page - `name: map[string]` Localized board name (language code → text) - `submitButtonText: map[string]` Submit button text - `name: string` Display name in organization's default locale - `object: "board"` Object type identifier - `"board"` - `postDefaults: object { visibility }` - `visibility: "public" or "authorOnly" or "companyOnly"` Default visibility for new posts - `"public"` - `"authorOnly"` - `"companyOnly"` ### Example ```http curl https://do.featurebase.app/v2/boards/$ID \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "access": { "adminOnly": false, "allowedRoles": [ "string" ], "deniedRoles": [ "role_blocked" ], "segments": [ "string" ] }, "createdAt": "2023-12-12T00:00:00.000Z", "customFields": [ "cf_priority", "cf_category" ], "features": { "commentsEnabled": true, "createdDatesVisible": true, "postingEnabled": true }, "icon": { "type": "emoji", "value": "💡" }, "localization": { "description": { "en": "Submit and vote on feature ideas" }, "formPlaceholder": { "en": "Describe your feature idea..." }, "heroDescription": { "foo": "string" }, "heroTitle": { "en": "Share your ideas" }, "name": { "en": "Feature Requests", "es": "Solicitudes de funciones" }, "submitButtonText": { "en": "Submit Feedback" } }, "name": "Feature Requests", "object": "board", "postDefaults": { "visibility": "public" } } ``` ## Domain Types ### Board - `Board object { id, access, createdAt, 7 more }` - `id: string` Unique identifier - `access: object { adminOnly, allowedRoles, deniedRoles, segments }` - `adminOnly: boolean` If true, only admins can see this board - `allowedRoles: array of string` Role IDs allowed access (empty = all) - `deniedRoles: array of string` Role IDs explicitly denied access - `segments: array of string` Segment IDs that can access (empty = all) - `createdAt: string` ISO 8601 timestamp when created - `customFields: array of string` Custom field IDs attached to this board - `features: object { commentsEnabled, createdDatesVisible, postingEnabled }` - `commentsEnabled: boolean` Whether users can comment on posts - `createdDatesVisible: boolean` Whether creation dates are visible on posts - `postingEnabled: boolean` Whether users can create new posts - `icon: object { type, value } or object { type, value } or object { type, value }` The board's icon. Can be one of three types: - **emoji**: A single emoji character (e.g., 💡, 🚀, ⭐) - **icon**: A predefined icon from the built-in library (e.g., lightbulb, bug, star) - **url**: A custom image URL (HTTPS required) Can be `null` if no icon is set. - `EmojiIcon object { type, value }` An emoji character as the board icon - `type: "emoji"` Emoji icon type - `"emoji"` - `value: string` A single emoji character - `PredefinedIcon object { type, value }` A predefined icon from the built-in icon library - `type: "icon"` Predefined icon type - `"icon"` - `value: string` Icon name from the predefined icon set (e.g., lightbulb, bug, star, rocket, flag, heart, check, question, megaphone, gift) - `URLIcon object { type, value }` A custom icon loaded from an external URL - `type: "url"` External URL icon type - `"url"` - `value: string` HTTPS URL to a custom icon image (PNG, SVG, or WebP recommended) - `localization: object { description, formPlaceholder, heroDescription, 3 more }` - `description: map[string]` Localized description - `formPlaceholder: map[string]` Placeholder text in the post creation form - `heroDescription: map[string]` Hero description/subtitle - `heroTitle: map[string]` Hero title shown on the board page - `name: map[string]` Localized board name (language code → text) - `submitButtonText: map[string]` Submit button text - `name: string` Display name in organization's default locale - `object: "board"` Object type identifier - `"board"` - `postDefaults: object { visibility }` - `visibility: "public" or "authorOnly" or "companyOnly"` Default visibility for new posts - `"public"` - `"authorOnly"` - `"companyOnly"` ### Board List Response - `BoardListResponse = array of Board` - `id: string` Unique identifier - `access: object { adminOnly, allowedRoles, deniedRoles, segments }` - `adminOnly: boolean` If true, only admins can see this board - `allowedRoles: array of string` Role IDs allowed access (empty = all) - `deniedRoles: array of string` Role IDs explicitly denied access - `segments: array of string` Segment IDs that can access (empty = all) - `createdAt: string` ISO 8601 timestamp when created - `customFields: array of string` Custom field IDs attached to this board - `features: object { commentsEnabled, createdDatesVisible, postingEnabled }` - `commentsEnabled: boolean` Whether users can comment on posts - `createdDatesVisible: boolean` Whether creation dates are visible on posts - `postingEnabled: boolean` Whether users can create new posts - `icon: object { type, value } or object { type, value } or object { type, value }` The board's icon. Can be one of three types: - **emoji**: A single emoji character (e.g., 💡, 🚀, ⭐) - **icon**: A predefined icon from the built-in library (e.g., lightbulb, bug, star) - **url**: A custom image URL (HTTPS required) Can be `null` if no icon is set. - `EmojiIcon object { type, value }` An emoji character as the board icon - `type: "emoji"` Emoji icon type - `"emoji"` - `value: string` A single emoji character - `PredefinedIcon object { type, value }` A predefined icon from the built-in icon library - `type: "icon"` Predefined icon type - `"icon"` - `value: string` Icon name from the predefined icon set (e.g., lightbulb, bug, star, rocket, flag, heart, check, question, megaphone, gift) - `URLIcon object { type, value }` A custom icon loaded from an external URL - `type: "url"` External URL icon type - `"url"` - `value: string` HTTPS URL to a custom icon image (PNG, SVG, or WebP recommended) - `localization: object { description, formPlaceholder, heroDescription, 3 more }` - `description: map[string]` Localized description - `formPlaceholder: map[string]` Placeholder text in the post creation form - `heroDescription: map[string]` Hero description/subtitle - `heroTitle: map[string]` Hero title shown on the board page - `name: map[string]` Localized board name (language code → text) - `submitButtonText: map[string]` Submit button text - `name: string` Display name in organization's default locale - `object: "board"` Object type identifier - `"board"` - `postDefaults: object { visibility }` - `visibility: "public" or "authorOnly" or "companyOnly"` Default visibility for new posts - `"public"` - `"authorOnly"` - `"companyOnly"` # Posts ## List all posts **get** `/v2/posts` Returns all posts (feedback submissions) for the authenticated organization. Posts are user-submitted feedback items. Each post belongs to a board and can have: - Status (in progress, complete, etc.) - Tags for categorization - Upvotes from users - Comments (if enabled) - Custom field values ### Pagination This endpoint uses **cursor-based pagination**: - `limit` - Number of posts 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/posts?limit=10` 1. If `nextCursor` is not null, use it for the next page 1. Next request: `GET /v2/posts?limit=10&cursor={nextCursor}` ### Response Format Returns a list object with: - `object` - Always "list" - `data` - Array of post objects - `nextCursor` - Cursor for the next page (null if no more results) ### Filtering Filter posts using query parameters: - `boardId` - Filter by board (category) ID - `statusId` - Filter by status ID - `tags` - Filter by tag names (can be comma-separated or repeated) - `q` - Search query for title/content - `inReview` - Include posts pending moderation ### Sorting Use `sortBy` to sort results: - `createdAt` - Sort by creation date (default) - `upvotes` - Sort by vote count - `trending` - Sort by trending score - `recent` - Sort by most recently updated ### Query Parameters - `boardId: optional string or array of string` Filter by board (category) ID(s) - `string` - `array of string` - `cursor: optional string` An opaque cursor for pagination. Use the nextCursor value from a previous response to fetch the next page of results. - `inReview: optional boolean` Include posts that are in review - `limit: optional number` A limit on the number of posts to be returned, between 1 and 100. - `q: optional string` Search query to filter posts by title/content - `sortBy: optional "createdAt" or "upvotes" or "trending" or "recent"` Sort order for posts - `"createdAt"` - `"upvotes"` - `"trending"` - `"recent"` - `sortOrder: optional "asc" or "desc"` Sort direction - `"asc"` - `"desc"` - `statusId: optional string or array of string` Filter by status ID(s) - `string` - `array of string` - `tags: optional string or array of string` Filter by tag names - `string` - `array of string` ### 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 Post` Array of posts - `id: string` Unique identifier - `access: object { companyExternalIds, userIds }` - `companyExternalIds: array of string` External company IDs explicitly granted access to this post. Empty array means no company-level restrictions. Non-empty means only users belonging to these companies can see the post. - `userIds: array of string` User IDs explicitly granted access to this post. Empty array means no user-level restrictions (post uses board/org visibility). Non-empty means only these users (plus admins) can see the post. - `assigneeId: string` ID of the admin assigned to this post, null if unassigned - `author: object { id, email, name, 2 more }` - `id: string` Author unique identifier - `email: string` Author email (if available) - `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 post - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `boardId: string` Board (category) ID this post belongs to - `commentCount: number` Total number of comments - `content: string` Post content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `customFields: map[unknown]` Custom field values keyed by field ID - `eta: string` Estimated completion time as ISO 8601 timestamp, null if not set - `features: object { commentsEnabled }` - `commentsEnabled: boolean` Whether comments are allowed on this post - `inReview: boolean` Whether the post is pending moderation review - `integrations: object { clickup, devops, github, 4 more }` Third-party integration links associated with this post - `clickup: array of object { id, title, url }` - `id: string` ClickUp task ID - `title: string` ClickUp task title - `url: string` URL to the ClickUp task - `devops: array of object { id, projectId, projectName, 2 more }` - `id: number` Azure DevOps work item ID - `projectId: string` Azure DevOps project ID - `projectName: string` Azure DevOps project name - `title: string` Work item title - `url: string` URL to the work item - `github: array of object { id, number, repositoryFullName, 3 more }` - `id: string` GitHub issue ID - `number: string` GitHub issue number - `repositoryFullName: string` Full repository name (owner/repo) - `repositoryName: string` Repository name - `title: string` GitHub issue title - `url: string` URL to the GitHub issue - `hubspot: array of object { dealAmount, dealClosed, objectId, type }` - `dealAmount: number` Deal amount (for DEAL type) - `dealClosed: boolean` Whether the deal is closed (for DEAL type) - `objectId: number` HubSpot object ID - `type: "TICKET" or "DEAL" or "CONTACT"` HubSpot object type - `"TICKET"` - `"DEAL"` - `"CONTACT"` - `jira: array of object { issueId, issueUrl }` - `issueId: string` Jira issue ID - `issueUrl: string` URL to the Jira issue - `linear: array of object { issueId, issueUrl }` - `issueId: string` Linear issue ID - `issueUrl: string` URL to the Linear issue - `salesforce: array of object { amount, isClosed, objectId, objectType }` - `amount: number` Opportunity amount (for Opportunity type) - `isClosed: boolean` Whether the opportunity is closed (for Opportunity type) - `objectId: string` Salesforce record ID - `objectType: "Opportunity" or "Case"` Salesforce object type - `"Opportunity"` - `"Case"` - `isPinned: boolean` Whether the post is pinned to the top - `object: "post"` Object type identifier - `"post"` - `opportunityAmount: number` Total opportunity amount from linked HubSpot deals and Salesforce opportunities - `postUrl: string` Full URL to view the post - `slug: string` URL-friendly slug - `status: PostStatus` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` - `tags: array of object { id, color, name }` Tags attached to this post - `id: string` Tag unique identifier - `color: string` Tag color hex code - `name: string` Tag name - `title: string` Post title - `updatedAt: string` ISO 8601 timestamp when last modified - `upvotes: number` Total 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/posts \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "data": [ { "id": "507f1f77bcf86cd799439011", "access": { "companyExternalIds": [ "string" ], "userIds": [ "string" ] }, "assigneeId": "507f1f77bcf86cd799439013", "author": { "id": "507f1f77bcf86cd799439011", "email": "john@example.com", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "boardId": "507f1f77bcf86cd799439011", "commentCount": 5, "content": "

It would be great to have a dark mode option for the dashboard.

", "createdAt": "2023-12-12T00:00:00.000Z", "customFields": { "cf_priority": "bar", "cf_effort": "bar" }, "eta": "2025-01-01T00:00:00.000Z", "features": { "commentsEnabled": true }, "inReview": false, "integrations": { "clickup": [ { "id": "86a1b2c3d", "title": "Add dark mode support", "url": "https://app.clickup.com/t/86a1b2c3d" } ], "devops": [ { "id": 1234, "projectId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "projectName": "My Project", "title": "Add dark mode support", "url": "https://dev.azure.com/org/project/_workitems/edit/1234" } ], "github": [ { "id": "1234567890", "number": "42", "repositoryFullName": "acme/backend", "repositoryName": "backend", "title": "Add dark mode support", "url": "https://github.com/acme/backend/issues/42" } ], "hubspot": [ { "dealAmount": 5000, "dealClosed": false, "objectId": 123456789, "type": "TICKET" } ], "jira": [ { "issueId": "10042", "issueUrl": "https://myteam.atlassian.net/browse/PROJ-123" } ], "linear": [ { "issueId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "issueUrl": "https://linear.app/team/issue/ENG-123" } ], "salesforce": [ { "amount": 25000, "isClosed": false, "objectId": "006Dn00000Abcdef", "objectType": "Opportunity" } ] }, "isPinned": false, "object": "post", "opportunityAmount": 30000, "postUrl": "https://feedback.example.com/p/add-dark-mode-support", "slug": "add-dark-mode-support", "status": { "id": "507f1f77bcf86cd799439011", "color": "Blue", "isDefault": false, "name": "In Progress", "object": "post_status", "type": "active" }, "tags": [ { "id": "507f1f77bcf86cd799439011", "color": "#FF5722", "name": "bug" } ], "title": "Add dark mode support", "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 42 } ], "nextCursor": "eyJpZCI6IjUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMSJ9", "object": "list", "pagination": { "limit": 10, "page": 1, "total": 42, "totalPages": 5 } } ``` ## Create a new post **post** `/v2/posts` Creates a new post (feedback submission) in the specified board. ### Required Fields - `title` - Post title (minimum 2 characters) - `boardId` - Board ID to create the post in ### Optional Fields - `content` - Post content in HTML format - `tags` - Array of tag names to attach - `statusId` - Status ID to set (defaults to board's default status) - `commentsEnabled` - Whether comments are allowed (default: true) - `inReview` - Whether post is pending moderation (default: false) - `customFields` - Custom field values as key-value pairs - `eta` - Estimated completion date (Unix timestamp or ISO date) - `assigneeId` - Admin ID to assign this post to - `visibility` - Post-level visibility restriction: 'public' (no additional restrictions), 'authorOnly' (only author and admins), or 'companyOnly' (only users in author's company). Note: even 'public' posts are still subject to board-level and organization-level access controls. ### Author Attribution For posts created on behalf of users, use the `author` object: - `id` - Featurebase user ID - `userId` - External SSO user ID - `email` - User's email address - `name` - Display name - `profilePicture` - Profile picture URL Resolution priority: `id` > `userId` > `email` > authenticated user ### Backdating (Imports) - `createdAt` - Override creation date for importing historical data ### Response Returns the created post object with all fields populated. ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Body Parameters - `boardId: string` Board ID to create post in - `title: string` Post title - `assigneeId: optional string` Admin ID to assign this post to - `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) - `commentsEnabled: optional boolean` Whether comments are enabled on this post - `content: optional string` Post content (HTML) - `createdAt: optional string` Creation date (for backdating imports) - `customFields: optional map[array of string or boolean or number or 2 more]` Custom field values. Keys must be valid ObjectIds. Values can be: string, boolean, number, ISO date string, array of strings, ObjectId string, or null. - `array of string` - `boolean` - `number` - `string` - `string` - `eta: optional string` Estimated completion date - `inReview: optional boolean` Whether post is pending moderation - `integrations: optional object { clickup, discord, github, 3 more }` Push the created post to third-party integrations configured on your organization. Each integration must be explicitly set to true to trigger; omitted integrations will not be pushed to. - `clickup: optional boolean` Push to ClickUp - `discord: optional boolean` Push to Discord - `github: optional boolean` Push to GitHub - `jira: optional boolean` Push to Jira - `linear: optional boolean` Push to Linear - `slack: optional boolean` Push to Slack - `notifyAdmins: optional boolean` Whether to send email notifications to admins when this post is created. When true, admins will receive the same email notifications as when a post is created from the dashboard. Defaults to false (no emails sent). - `statusId: optional string` Status ID to set - `tags: optional string or array of string` Tag names to attach - `string` - `array of string` - `upvotes: optional number` Initial upvotes count. Defaults to 1 (post author is automatically added as voter). Use 0 to create a post without any votes. - `visibility: optional "public" or "authorOnly" or "companyOnly"` Post visibility. 'public' = visible to all users, 'authorOnly' = only visible to the author and admins, 'companyOnly' = only visible to users in the same company as the author - `"public"` - `"authorOnly"` - `"companyOnly"` ### Returns - `Post object { id, access, assigneeId, 20 more }` - `id: string` Unique identifier - `access: object { companyExternalIds, userIds }` - `companyExternalIds: array of string` External company IDs explicitly granted access to this post. Empty array means no company-level restrictions. Non-empty means only users belonging to these companies can see the post. - `userIds: array of string` User IDs explicitly granted access to this post. Empty array means no user-level restrictions (post uses board/org visibility). Non-empty means only these users (plus admins) can see the post. - `assigneeId: string` ID of the admin assigned to this post, null if unassigned - `author: object { id, email, name, 2 more }` - `id: string` Author unique identifier - `email: string` Author email (if available) - `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 post - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `boardId: string` Board (category) ID this post belongs to - `commentCount: number` Total number of comments - `content: string` Post content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `customFields: map[unknown]` Custom field values keyed by field ID - `eta: string` Estimated completion time as ISO 8601 timestamp, null if not set - `features: object { commentsEnabled }` - `commentsEnabled: boolean` Whether comments are allowed on this post - `inReview: boolean` Whether the post is pending moderation review - `integrations: object { clickup, devops, github, 4 more }` Third-party integration links associated with this post - `clickup: array of object { id, title, url }` - `id: string` ClickUp task ID - `title: string` ClickUp task title - `url: string` URL to the ClickUp task - `devops: array of object { id, projectId, projectName, 2 more }` - `id: number` Azure DevOps work item ID - `projectId: string` Azure DevOps project ID - `projectName: string` Azure DevOps project name - `title: string` Work item title - `url: string` URL to the work item - `github: array of object { id, number, repositoryFullName, 3 more }` - `id: string` GitHub issue ID - `number: string` GitHub issue number - `repositoryFullName: string` Full repository name (owner/repo) - `repositoryName: string` Repository name - `title: string` GitHub issue title - `url: string` URL to the GitHub issue - `hubspot: array of object { dealAmount, dealClosed, objectId, type }` - `dealAmount: number` Deal amount (for DEAL type) - `dealClosed: boolean` Whether the deal is closed (for DEAL type) - `objectId: number` HubSpot object ID - `type: "TICKET" or "DEAL" or "CONTACT"` HubSpot object type - `"TICKET"` - `"DEAL"` - `"CONTACT"` - `jira: array of object { issueId, issueUrl }` - `issueId: string` Jira issue ID - `issueUrl: string` URL to the Jira issue - `linear: array of object { issueId, issueUrl }` - `issueId: string` Linear issue ID - `issueUrl: string` URL to the Linear issue - `salesforce: array of object { amount, isClosed, objectId, objectType }` - `amount: number` Opportunity amount (for Opportunity type) - `isClosed: boolean` Whether the opportunity is closed (for Opportunity type) - `objectId: string` Salesforce record ID - `objectType: "Opportunity" or "Case"` Salesforce object type - `"Opportunity"` - `"Case"` - `isPinned: boolean` Whether the post is pinned to the top - `object: "post"` Object type identifier - `"post"` - `opportunityAmount: number` Total opportunity amount from linked HubSpot deals and Salesforce opportunities - `postUrl: string` Full URL to view the post - `slug: string` URL-friendly slug - `status: PostStatus` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` - `tags: array of object { id, color, name }` Tags attached to this post - `id: string` Tag unique identifier - `color: string` Tag color hex code - `name: string` Tag name - `title: string` Post title - `updatedAt: string` ISO 8601 timestamp when last modified - `upvotes: number` Total number of upvotes ### Example ```http curl https://do.featurebase.app/v2/posts \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" \ -d '{ "boardId": "507f1f77bcf86cd799439011", "title": "Add dark mode support", "assigneeId": "507f1f77bcf86cd799439013", "commentsEnabled": true, "content": "

It would be great to have dark mode.

", "createdAt": "2025-01-15T10:30:00.000Z", "customFields": { "507f1f77bcf86cd799439011": "high" }, "eta": "2025-12-31T23:59:59.000Z", "notifyAdmins": true, "statusId": "507f1f77bcf86cd799439012", "tags": [ "feature", "ui" ], "upvotes": 5, "visibility": "public" }' ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "access": { "companyExternalIds": [ "string" ], "userIds": [ "string" ] }, "assigneeId": "507f1f77bcf86cd799439013", "author": { "id": "507f1f77bcf86cd799439011", "email": "john@example.com", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "boardId": "507f1f77bcf86cd799439011", "commentCount": 5, "content": "

It would be great to have a dark mode option for the dashboard.

", "createdAt": "2023-12-12T00:00:00.000Z", "customFields": { "cf_priority": "bar", "cf_effort": "bar" }, "eta": "2025-01-01T00:00:00.000Z", "features": { "commentsEnabled": true }, "inReview": false, "integrations": { "clickup": [ { "id": "86a1b2c3d", "title": "Add dark mode support", "url": "https://app.clickup.com/t/86a1b2c3d" } ], "devops": [ { "id": 1234, "projectId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "projectName": "My Project", "title": "Add dark mode support", "url": "https://dev.azure.com/org/project/_workitems/edit/1234" } ], "github": [ { "id": "1234567890", "number": "42", "repositoryFullName": "acme/backend", "repositoryName": "backend", "title": "Add dark mode support", "url": "https://github.com/acme/backend/issues/42" } ], "hubspot": [ { "dealAmount": 5000, "dealClosed": false, "objectId": 123456789, "type": "TICKET" } ], "jira": [ { "issueId": "10042", "issueUrl": "https://myteam.atlassian.net/browse/PROJ-123" } ], "linear": [ { "issueId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "issueUrl": "https://linear.app/team/issue/ENG-123" } ], "salesforce": [ { "amount": 25000, "isClosed": false, "objectId": "006Dn00000Abcdef", "objectType": "Opportunity" } ] }, "isPinned": false, "object": "post", "opportunityAmount": 30000, "postUrl": "https://feedback.example.com/p/add-dark-mode-support", "slug": "add-dark-mode-support", "status": { "id": "507f1f77bcf86cd799439011", "color": "Blue", "isDefault": false, "name": "In Progress", "object": "post_status", "type": "active" }, "tags": [ { "id": "507f1f77bcf86cd799439011", "color": "#FF5722", "name": "bug" } ], "title": "Add dark mode support", "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 42 } ``` ## Get a post by ID **get** `/v2/posts/{id}` Retrieves a single post by its unique identifier. Returns the full post object including: - Author information - Current status - Tags - Voting stats - Engagement metrics - Custom field values ### Path Parameters - `id: string` Post 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 - `Post object { id, access, assigneeId, 20 more }` - `id: string` Unique identifier - `access: object { companyExternalIds, userIds }` - `companyExternalIds: array of string` External company IDs explicitly granted access to this post. Empty array means no company-level restrictions. Non-empty means only users belonging to these companies can see the post. - `userIds: array of string` User IDs explicitly granted access to this post. Empty array means no user-level restrictions (post uses board/org visibility). Non-empty means only these users (plus admins) can see the post. - `assigneeId: string` ID of the admin assigned to this post, null if unassigned - `author: object { id, email, name, 2 more }` - `id: string` Author unique identifier - `email: string` Author email (if available) - `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 post - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `boardId: string` Board (category) ID this post belongs to - `commentCount: number` Total number of comments - `content: string` Post content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `customFields: map[unknown]` Custom field values keyed by field ID - `eta: string` Estimated completion time as ISO 8601 timestamp, null if not set - `features: object { commentsEnabled }` - `commentsEnabled: boolean` Whether comments are allowed on this post - `inReview: boolean` Whether the post is pending moderation review - `integrations: object { clickup, devops, github, 4 more }` Third-party integration links associated with this post - `clickup: array of object { id, title, url }` - `id: string` ClickUp task ID - `title: string` ClickUp task title - `url: string` URL to the ClickUp task - `devops: array of object { id, projectId, projectName, 2 more }` - `id: number` Azure DevOps work item ID - `projectId: string` Azure DevOps project ID - `projectName: string` Azure DevOps project name - `title: string` Work item title - `url: string` URL to the work item - `github: array of object { id, number, repositoryFullName, 3 more }` - `id: string` GitHub issue ID - `number: string` GitHub issue number - `repositoryFullName: string` Full repository name (owner/repo) - `repositoryName: string` Repository name - `title: string` GitHub issue title - `url: string` URL to the GitHub issue - `hubspot: array of object { dealAmount, dealClosed, objectId, type }` - `dealAmount: number` Deal amount (for DEAL type) - `dealClosed: boolean` Whether the deal is closed (for DEAL type) - `objectId: number` HubSpot object ID - `type: "TICKET" or "DEAL" or "CONTACT"` HubSpot object type - `"TICKET"` - `"DEAL"` - `"CONTACT"` - `jira: array of object { issueId, issueUrl }` - `issueId: string` Jira issue ID - `issueUrl: string` URL to the Jira issue - `linear: array of object { issueId, issueUrl }` - `issueId: string` Linear issue ID - `issueUrl: string` URL to the Linear issue - `salesforce: array of object { amount, isClosed, objectId, objectType }` - `amount: number` Opportunity amount (for Opportunity type) - `isClosed: boolean` Whether the opportunity is closed (for Opportunity type) - `objectId: string` Salesforce record ID - `objectType: "Opportunity" or "Case"` Salesforce object type - `"Opportunity"` - `"Case"` - `isPinned: boolean` Whether the post is pinned to the top - `object: "post"` Object type identifier - `"post"` - `opportunityAmount: number` Total opportunity amount from linked HubSpot deals and Salesforce opportunities - `postUrl: string` Full URL to view the post - `slug: string` URL-friendly slug - `status: PostStatus` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` - `tags: array of object { id, color, name }` Tags attached to this post - `id: string` Tag unique identifier - `color: string` Tag color hex code - `name: string` Tag name - `title: string` Post title - `updatedAt: string` ISO 8601 timestamp when last modified - `upvotes: number` Total number of upvotes ### Example ```http curl https://do.featurebase.app/v2/posts/$ID \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "access": { "companyExternalIds": [ "string" ], "userIds": [ "string" ] }, "assigneeId": "507f1f77bcf86cd799439013", "author": { "id": "507f1f77bcf86cd799439011", "email": "john@example.com", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "boardId": "507f1f77bcf86cd799439011", "commentCount": 5, "content": "

It would be great to have a dark mode option for the dashboard.

", "createdAt": "2023-12-12T00:00:00.000Z", "customFields": { "cf_priority": "bar", "cf_effort": "bar" }, "eta": "2025-01-01T00:00:00.000Z", "features": { "commentsEnabled": true }, "inReview": false, "integrations": { "clickup": [ { "id": "86a1b2c3d", "title": "Add dark mode support", "url": "https://app.clickup.com/t/86a1b2c3d" } ], "devops": [ { "id": 1234, "projectId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "projectName": "My Project", "title": "Add dark mode support", "url": "https://dev.azure.com/org/project/_workitems/edit/1234" } ], "github": [ { "id": "1234567890", "number": "42", "repositoryFullName": "acme/backend", "repositoryName": "backend", "title": "Add dark mode support", "url": "https://github.com/acme/backend/issues/42" } ], "hubspot": [ { "dealAmount": 5000, "dealClosed": false, "objectId": 123456789, "type": "TICKET" } ], "jira": [ { "issueId": "10042", "issueUrl": "https://myteam.atlassian.net/browse/PROJ-123" } ], "linear": [ { "issueId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "issueUrl": "https://linear.app/team/issue/ENG-123" } ], "salesforce": [ { "amount": 25000, "isClosed": false, "objectId": "006Dn00000Abcdef", "objectType": "Opportunity" } ] }, "isPinned": false, "object": "post", "opportunityAmount": 30000, "postUrl": "https://feedback.example.com/p/add-dark-mode-support", "slug": "add-dark-mode-support", "status": { "id": "507f1f77bcf86cd799439011", "color": "Blue", "isDefault": false, "name": "In Progress", "object": "post_status", "type": "active" }, "tags": [ { "id": "507f1f77bcf86cd799439011", "color": "#FF5722", "name": "bug" } ], "title": "Add dark mode support", "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 42 } ``` ## Update a post **patch** `/v2/posts/{id}` Updates an existing post. Only provided fields will be modified. ### Updatable Fields - `title` - Post title (minimum 2 characters) - `content` - Post content in HTML format - `boardId` - Move post to a different board - `statusId` - Update post status - `tags` - Replace existing tags with new set - `commentsEnabled` - Enable/disable comments - `inReview` - Put post in/out of moderation queue - `customFields` - Update custom field values - `eta` - Set estimated completion date (null to clear) - `createdAt` - Update creation date (for backdating) - `assigneeId` - Admin ID to assign this post to (null to unassign) - `visibility` - Post-level visibility restriction: 'public' (no additional restrictions), 'authorOnly' (only author and admins), or 'companyOnly' (only users in author's company). Note: even 'public' posts are still subject to board-level and organization-level access controls. - `author` - Change post attribution (id, userId, email, name, profilePicture) ### Status Update Notifications - `sendStatusUpdateEmail` - When changing status, optionally send email notification to voters (default: false) ### Response Returns the updated post object with all fields populated. ### Path Parameters - `id: string` Post 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 - `assigneeId: optional string` Admin ID to assign this post to (null to unassign) - `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) - `boardId: optional string` Board ID to move post to - `commentsEnabled: optional boolean` Whether comments are enabled on this post - `content: optional string` Post content (HTML) - `createdAt: optional string` Creation date (for backdating) - `customFields: optional map[array of string or boolean or number or 2 more]` Custom field values. Keys must be valid ObjectIds. Values can be: string, boolean, number, ISO date string, array of strings, ObjectId string, or null. - `array of string` - `boolean` - `number` - `string` - `string` - `eta: optional string` Estimated completion date (null to clear) - `inReview: optional boolean` Whether post is pending moderation - `sendStatusUpdateEmail: optional boolean` Whether to send status update email to voters - `statusId: optional string` Status ID to set - `tags: optional string or array of string` Tag names to set (replaces existing) - `string` - `array of string` - `title: optional string` Post title - `upvotes: optional number` Set the upvotes count directly. Use with caution as this overrides the actual vote count. - `visibility: optional "public" or "authorOnly" or "companyOnly"` Post visibility. 'public' = visible to all users, 'authorOnly' = only visible to the author and admins, 'companyOnly' = only visible to users in the same company as the author - `"public"` - `"authorOnly"` - `"companyOnly"` ### Returns - `Post object { id, access, assigneeId, 20 more }` - `id: string` Unique identifier - `access: object { companyExternalIds, userIds }` - `companyExternalIds: array of string` External company IDs explicitly granted access to this post. Empty array means no company-level restrictions. Non-empty means only users belonging to these companies can see the post. - `userIds: array of string` User IDs explicitly granted access to this post. Empty array means no user-level restrictions (post uses board/org visibility). Non-empty means only these users (plus admins) can see the post. - `assigneeId: string` ID of the admin assigned to this post, null if unassigned - `author: object { id, email, name, 2 more }` - `id: string` Author unique identifier - `email: string` Author email (if available) - `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 post - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `boardId: string` Board (category) ID this post belongs to - `commentCount: number` Total number of comments - `content: string` Post content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `customFields: map[unknown]` Custom field values keyed by field ID - `eta: string` Estimated completion time as ISO 8601 timestamp, null if not set - `features: object { commentsEnabled }` - `commentsEnabled: boolean` Whether comments are allowed on this post - `inReview: boolean` Whether the post is pending moderation review - `integrations: object { clickup, devops, github, 4 more }` Third-party integration links associated with this post - `clickup: array of object { id, title, url }` - `id: string` ClickUp task ID - `title: string` ClickUp task title - `url: string` URL to the ClickUp task - `devops: array of object { id, projectId, projectName, 2 more }` - `id: number` Azure DevOps work item ID - `projectId: string` Azure DevOps project ID - `projectName: string` Azure DevOps project name - `title: string` Work item title - `url: string` URL to the work item - `github: array of object { id, number, repositoryFullName, 3 more }` - `id: string` GitHub issue ID - `number: string` GitHub issue number - `repositoryFullName: string` Full repository name (owner/repo) - `repositoryName: string` Repository name - `title: string` GitHub issue title - `url: string` URL to the GitHub issue - `hubspot: array of object { dealAmount, dealClosed, objectId, type }` - `dealAmount: number` Deal amount (for DEAL type) - `dealClosed: boolean` Whether the deal is closed (for DEAL type) - `objectId: number` HubSpot object ID - `type: "TICKET" or "DEAL" or "CONTACT"` HubSpot object type - `"TICKET"` - `"DEAL"` - `"CONTACT"` - `jira: array of object { issueId, issueUrl }` - `issueId: string` Jira issue ID - `issueUrl: string` URL to the Jira issue - `linear: array of object { issueId, issueUrl }` - `issueId: string` Linear issue ID - `issueUrl: string` URL to the Linear issue - `salesforce: array of object { amount, isClosed, objectId, objectType }` - `amount: number` Opportunity amount (for Opportunity type) - `isClosed: boolean` Whether the opportunity is closed (for Opportunity type) - `objectId: string` Salesforce record ID - `objectType: "Opportunity" or "Case"` Salesforce object type - `"Opportunity"` - `"Case"` - `isPinned: boolean` Whether the post is pinned to the top - `object: "post"` Object type identifier - `"post"` - `opportunityAmount: number` Total opportunity amount from linked HubSpot deals and Salesforce opportunities - `postUrl: string` Full URL to view the post - `slug: string` URL-friendly slug - `status: PostStatus` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` - `tags: array of object { id, color, name }` Tags attached to this post - `id: string` Tag unique identifier - `color: string` Tag color hex code - `name: string` Tag name - `title: string` Post title - `updatedAt: string` ISO 8601 timestamp when last modified - `upvotes: number` Total number of upvotes ### Example ```http curl https://do.featurebase.app/v2/posts/$ID \ -X PATCH \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" \ -d '{ "assigneeId": "507f1f77bcf86cd799439013", "boardId": "507f1f77bcf86cd799439011", "commentsEnabled": true, "content": "

Updated content with more details.

", "createdAt": "2025-01-15T10:30:00.000Z", "customFields": { "507f1f77bcf86cd799439011": "high" }, "eta": "2025-12-31T23:59:59.000Z", "statusId": "507f1f77bcf86cd799439012", "tags": [ "feature", "ui" ], "title": "Updated: Add dark mode support", "upvotes": 10, "visibility": "public" }' ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "access": { "companyExternalIds": [ "string" ], "userIds": [ "string" ] }, "assigneeId": "507f1f77bcf86cd799439013", "author": { "id": "507f1f77bcf86cd799439011", "email": "john@example.com", "name": "John Doe", "profilePicture": "https://cdn.example.com/avatars/john.png", "type": "customer" }, "boardId": "507f1f77bcf86cd799439011", "commentCount": 5, "content": "

It would be great to have a dark mode option for the dashboard.

", "createdAt": "2023-12-12T00:00:00.000Z", "customFields": { "cf_priority": "bar", "cf_effort": "bar" }, "eta": "2025-01-01T00:00:00.000Z", "features": { "commentsEnabled": true }, "inReview": false, "integrations": { "clickup": [ { "id": "86a1b2c3d", "title": "Add dark mode support", "url": "https://app.clickup.com/t/86a1b2c3d" } ], "devops": [ { "id": 1234, "projectId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "projectName": "My Project", "title": "Add dark mode support", "url": "https://dev.azure.com/org/project/_workitems/edit/1234" } ], "github": [ { "id": "1234567890", "number": "42", "repositoryFullName": "acme/backend", "repositoryName": "backend", "title": "Add dark mode support", "url": "https://github.com/acme/backend/issues/42" } ], "hubspot": [ { "dealAmount": 5000, "dealClosed": false, "objectId": 123456789, "type": "TICKET" } ], "jira": [ { "issueId": "10042", "issueUrl": "https://myteam.atlassian.net/browse/PROJ-123" } ], "linear": [ { "issueId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "issueUrl": "https://linear.app/team/issue/ENG-123" } ], "salesforce": [ { "amount": 25000, "isClosed": false, "objectId": "006Dn00000Abcdef", "objectType": "Opportunity" } ] }, "isPinned": false, "object": "post", "opportunityAmount": 30000, "postUrl": "https://feedback.example.com/p/add-dark-mode-support", "slug": "add-dark-mode-support", "status": { "id": "507f1f77bcf86cd799439011", "color": "Blue", "isDefault": false, "name": "In Progress", "object": "post_status", "type": "active" }, "tags": [ { "id": "507f1f77bcf86cd799439011", "color": "#FF5722", "name": "bug" } ], "title": "Add dark mode support", "updatedAt": "2023-12-13T00:00:00.000Z", "upvotes": 42 } ``` ## Delete a post **delete** `/v2/posts/{id}` Permanently deletes a post. This action cannot be undone. ### What Gets Deleted When you delete a post: - The post itself is permanently removed - All comments on the post are deleted - Vote records are removed - Any associated notifications are cleared ### Response Returns a deletion confirmation object with: - `id` - The ID of the deleted post - `object` - Always "post" - `deleted` - Always true ### Permissions Requires member-level access or higher to delete posts. ### Path Parameters - `id: string` Post 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 post - `deleted: true` Indicates the resource was deleted - `true` - `object: "post"` Object type identifier - `"post"` ### Example ```http curl https://do.featurebase.app/v2/posts/$ID \ -X DELETE \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "deleted": true, "object": "post" } ``` ## Domain Types ### Post - `Post object { id, access, assigneeId, 20 more }` - `id: string` Unique identifier - `access: object { companyExternalIds, userIds }` - `companyExternalIds: array of string` External company IDs explicitly granted access to this post. Empty array means no company-level restrictions. Non-empty means only users belonging to these companies can see the post. - `userIds: array of string` User IDs explicitly granted access to this post. Empty array means no user-level restrictions (post uses board/org visibility). Non-empty means only these users (plus admins) can see the post. - `assigneeId: string` ID of the admin assigned to this post, null if unassigned - `author: object { id, email, name, 2 more }` - `id: string` Author unique identifier - `email: string` Author email (if available) - `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 post - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `boardId: string` Board (category) ID this post belongs to - `commentCount: number` Total number of comments - `content: string` Post content in HTML format - `createdAt: string` ISO 8601 timestamp when created - `customFields: map[unknown]` Custom field values keyed by field ID - `eta: string` Estimated completion time as ISO 8601 timestamp, null if not set - `features: object { commentsEnabled }` - `commentsEnabled: boolean` Whether comments are allowed on this post - `inReview: boolean` Whether the post is pending moderation review - `integrations: object { clickup, devops, github, 4 more }` Third-party integration links associated with this post - `clickup: array of object { id, title, url }` - `id: string` ClickUp task ID - `title: string` ClickUp task title - `url: string` URL to the ClickUp task - `devops: array of object { id, projectId, projectName, 2 more }` - `id: number` Azure DevOps work item ID - `projectId: string` Azure DevOps project ID - `projectName: string` Azure DevOps project name - `title: string` Work item title - `url: string` URL to the work item - `github: array of object { id, number, repositoryFullName, 3 more }` - `id: string` GitHub issue ID - `number: string` GitHub issue number - `repositoryFullName: string` Full repository name (owner/repo) - `repositoryName: string` Repository name - `title: string` GitHub issue title - `url: string` URL to the GitHub issue - `hubspot: array of object { dealAmount, dealClosed, objectId, type }` - `dealAmount: number` Deal amount (for DEAL type) - `dealClosed: boolean` Whether the deal is closed (for DEAL type) - `objectId: number` HubSpot object ID - `type: "TICKET" or "DEAL" or "CONTACT"` HubSpot object type - `"TICKET"` - `"DEAL"` - `"CONTACT"` - `jira: array of object { issueId, issueUrl }` - `issueId: string` Jira issue ID - `issueUrl: string` URL to the Jira issue - `linear: array of object { issueId, issueUrl }` - `issueId: string` Linear issue ID - `issueUrl: string` URL to the Linear issue - `salesforce: array of object { amount, isClosed, objectId, objectType }` - `amount: number` Opportunity amount (for Opportunity type) - `isClosed: boolean` Whether the opportunity is closed (for Opportunity type) - `objectId: string` Salesforce record ID - `objectType: "Opportunity" or "Case"` Salesforce object type - `"Opportunity"` - `"Case"` - `isPinned: boolean` Whether the post is pinned to the top - `object: "post"` Object type identifier - `"post"` - `opportunityAmount: number` Total opportunity amount from linked HubSpot deals and Salesforce opportunities - `postUrl: string` Full URL to view the post - `slug: string` URL-friendly slug - `status: PostStatus` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` - `tags: array of object { id, color, name }` Tags attached to this post - `id: string` Tag unique identifier - `color: string` Tag color hex code - `name: string` Tag name - `title: string` Post title - `updatedAt: string` ISO 8601 timestamp when last modified - `upvotes: number` Total number of upvotes ### Post Delete Response - `PostDeleteResponse object { id, deleted, object }` - `id: string` Unique identifier of the deleted post - `deleted: true` Indicates the resource was deleted - `true` - `object: "post"` Object type identifier - `"post"` # Voters ## List voters on a post **get** `/v2/posts/{id}/voters` Returns all voters (upvoters) for a specific post. Voters are users who have upvoted the post. Each voter is returned in the standard user format with: - Basic info: id, name, email, profilePicture - User type (admin, customer, guest, etc.) - Companies the user belongs to - Activity stats: commentsCreated, postsCreated, lastActivity - Preferences: subscribedToChangelog, locale, verified ### Pagination This endpoint uses **cursor-based pagination**: - `limit` - Number of voters 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/posts/{id}/voters?limit=10` 1. If `nextCursor` is not null, use it for the next page 1. Next request: `GET /v2/posts/{id}/voters?limit=10&cursor={nextCursor}` ### Response Format Returns a list object with: - `object` - Always "list" - `data` - Array of user objects - `nextCursor` - Cursor for the next page (null if no more results) ### Permissions Requires member-level access or higher. ### Path Parameters - `id: string` Post unique identifier ### Query Parameters - `cursor: optional string` An opaque cursor for pagination. Use the nextCursor value from a previous response to fetch the next page of results. - `limit: optional number` A limit on the number of voters to be returned, between 1 and 100. ### 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 object { id, name, object, 16 more }` Array of users - `id: string` Unique identifier - `name: string` User display name - `object: "contact"` Object type identifier - `"contact"` - `type: "admin" or "customer" or "guest" or 3 more` Type of user - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `commentsCreated: optional number` Number of comments created - `companies: optional array of Company` Companies the user belongs to - `id: string` Featurebase internal ID - `companyId: string` External company ID from your system - `companySize: number` Company employee headcount - `createdAt: string` ISO date when company was created - `industry: string` Industry - `lastActivity: string` ISO date of last activity - `linkedUsers: number` Number of users linked to this company - `monthlySpend: number` Monthly spend - `name: string` Company name - `object: "company"` Object type identifier - `"company"` - `plan: string` Plan or tier name - `updatedAt: string` ISO date when company was last updated - `website: string` Company website URL - `customFields: optional map[unknown]` Custom field values - `customFields: optional map[unknown]` Custom field values on the user - `description: optional string` User description/bio - `email: optional string` User email - `lastActivity: optional string` Last activity ISO timestamp - `locale: optional string` User locale - `manuallyOptedOutFromChangelog: optional boolean` Whether manually opted out from changelog - `organizationId: optional string` Organization ID the user belongs to - `postsCreated: optional number` Number of posts created - `profilePicture: optional string` Profile picture URL - `roles: optional array of string` User roles - `subscribedToChangelog: optional boolean` Whether subscribed to changelog - `userId: optional string` External user ID from SSO - `verified: optional boolean` Whether email is verified - `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/posts/$ID/voters \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "data": [ { "id": "676f0f6765bdaa7d7d760f88", "name": "John Steezy", "object": "contact", "type": "customer", "commentsCreated": 0, "companies": [ { "id": "507f1f77bcf86cd799439011", "companyId": "comp_12345", "companySize": 250, "createdAt": "2025-01-01T12:00:00.000Z", "industry": "Technology", "lastActivity": "2025-01-15T00:00:00.000Z", "linkedUsers": 15, "monthlySpend": 5000, "name": "Acme Inc", "object": "company", "plan": "enterprise", "updatedAt": "2025-01-10T15:30:00.000Z", "website": "https://acme.com", "customFields": { "location": "bar", "priority": "bar" } } ], "customFields": { "foo": "bar" }, "description": "", "email": "john@example.com", "lastActivity": "2025-01-03T21:42:30.181Z", "locale": "en", "manuallyOptedOutFromChangelog": false, "organizationId": "5febde12dc56d60012d47db6", "postsCreated": 0, "profilePicture": "https://fb-usercontent.fra1.cdn.digitaloceanspaces.com/anon_23.png", "roles": [ "string" ], "subscribedToChangelog": true, "userId": "676f0f673dbb299c8a4f3057", "verified": true } ], "nextCursor": "eyJpZCI6IjUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMSJ9", "object": "list", "pagination": { "limit": 10, "page": 1, "total": 42, "totalPages": 5 } } ``` ## Add a voter to a post **post** `/v2/posts/{id}/voters` Adds a voter (upvote) to a post. ### Voter Identification To add a vote on behalf of a user, provide one or more identification fields: - `id` - Featurebase user ID - `userId` - External SSO user ID from your system - `email` - User's email address - `name` - Display name (used when creating a new user) - `profilePicture` - Profile picture URL (used when creating a new user) Resolution priority: `id` > `userId` > `email` > authenticated user If no fields are provided, the authenticated user's vote is added. If the user doesn't exist, a new customer will be created with the provided information. ### Idempotency If the user has already voted on this post, the request succeeds but no duplicate vote is added. ### Response Returns a confirmation object with: - `object` - Always "voter" - `added` - Always true - `id` - The voter's user ID - `postId` - The post ID the vote was added to ### Permissions Requires member-level access or higher. ### Path Parameters - `id: string` Post 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 - `id: optional string` Featurebase user ID to add as voter - `email: optional string` Voter email (used to find or create user) - `name: optional string` Voter display name (used when creating new user) - `profilePicture: optional string` Voter profile picture URL - `userId: optional string` External user ID from your system (matched via SSO) ### Returns - `id: string` Voter unique identifier - `added: true` Indicates the voter was added - `true` - `object: "voter"` Object type identifier - `"voter"` - `postId: string` Post ID the voter was added to ### Example ```http curl https://do.featurebase.app/v2/posts/$ID/voters \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" \ -d '{ "id": "507f1f77bcf86cd799439011", "email": "john@example.com", "name": "John Doe", "profilePicture": "https://example.com/avatar.png", "userId": "usr_12345" }' ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "added": true, "object": "voter", "postId": "507f1f77bcf86cd799439012" } ``` ## Remove a voter from a post **delete** `/v2/posts/{id}/voters` Removes a voter (upvote) from a post. ### Voter Identification To remove a vote on behalf of a user, provide one or more identification fields: - `id` - Featurebase user ID - `userId` - External SSO user ID from your system - `email` - User's email address Resolution priority: `id` > `userId` > `email` > authenticated user If no fields are provided, the authenticated user's vote will be removed. ### Response Returns a confirmation object with: - `object` - Always "voter" - `removed` - Always true - `id` - The voter's user ID - `postId` - The post ID the vote was removed from ### Permissions Requires member-level access or higher. ### Path Parameters - `id: string` Post 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 - `id: optional string` Featurebase user ID to remove as voter - `email: optional string` Voter email to identify user - `userId: optional string` External user ID from your system (matched via SSO) ### Returns - `id: string` Voter unique identifier - `object: "voter"` Object type identifier - `"voter"` - `postId: string` Post ID the voter was removed from - `removed: true` Indicates the voter was removed - `true` ### Example ```http curl https://do.featurebase.app/v2/posts/$ID/voters \ -X DELETE \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "object": "voter", "postId": "507f1f77bcf86cd799439012", "removed": true } ``` ## Domain Types ### Voter List Response - `VoterListResponse object { id, name, object, 16 more }` - `id: string` Unique identifier - `name: string` User display name - `object: "contact"` Object type identifier - `"contact"` - `type: "admin" or "customer" or "guest" or 3 more` Type of user - `"admin"` - `"customer"` - `"guest"` - `"integration"` - `"bot"` - `"lead"` - `commentsCreated: optional number` Number of comments created - `companies: optional array of Company` Companies the user belongs to - `id: string` Featurebase internal ID - `companyId: string` External company ID from your system - `companySize: number` Company employee headcount - `createdAt: string` ISO date when company was created - `industry: string` Industry - `lastActivity: string` ISO date of last activity - `linkedUsers: number` Number of users linked to this company - `monthlySpend: number` Monthly spend - `name: string` Company name - `object: "company"` Object type identifier - `"company"` - `plan: string` Plan or tier name - `updatedAt: string` ISO date when company was last updated - `website: string` Company website URL - `customFields: optional map[unknown]` Custom field values - `customFields: optional map[unknown]` Custom field values on the user - `description: optional string` User description/bio - `email: optional string` User email - `lastActivity: optional string` Last activity ISO timestamp - `locale: optional string` User locale - `manuallyOptedOutFromChangelog: optional boolean` Whether manually opted out from changelog - `organizationId: optional string` Organization ID the user belongs to - `postsCreated: optional number` Number of posts created - `profilePicture: optional string` Profile picture URL - `roles: optional array of string` User roles - `subscribedToChangelog: optional boolean` Whether subscribed to changelog - `userId: optional string` External user ID from SSO - `verified: optional boolean` Whether email is verified ### Voter Add Response - `VoterAddResponse object { id, added, object, postId }` - `id: string` Voter unique identifier - `added: true` Indicates the voter was added - `true` - `object: "voter"` Object type identifier - `"voter"` - `postId: string` Post ID the voter was added to ### Voter Remove Response - `VoterRemoveResponse object { id, object, postId, removed }` - `id: string` Voter unique identifier - `object: "voter"` Object type identifier - `"voter"` - `postId: string` Post ID the voter was removed from - `removed: true` Indicates the voter was removed - `true` # Post Statuses ## List all post statuses **get** `/v2/post_statuses` Returns all post statuses for the authenticated organization. Post statuses define workflow stages for posts. Each status has: - A display name and color - A type indicating the workflow stage (reviewing, unstarted, active, completed, canceled) - A flag indicating if it's the default status for new posts This endpoint returns all post statuses without pagination. Organizations typically have a small number of statuses. ### 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 - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` ### Example ```http curl https://do.featurebase.app/v2/post_statuses \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json [ { "id": "507f1f77bcf86cd799439011", "color": "Blue", "isDefault": false, "name": "In Progress", "object": "post_status", "type": "active" } ] ``` ## Get a post status by ID **get** `/v2/post_statuses/{id}` Retrieves a single post status by its unique identifier. Returns the full post status object including name, color, type, and default flag. ### Path Parameters - `id: string` Post status 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 - `PostStatus object { id, color, isDefault, 3 more }` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` ### Example ```http curl https://do.featurebase.app/v2/post_statuses/$ID \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "507f1f77bcf86cd799439011", "color": "Blue", "isDefault": false, "name": "In Progress", "object": "post_status", "type": "active" } ``` ## Domain Types ### Post Status - `PostStatus object { id, color, isDefault, 3 more }` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` ### Post Status List Response - `PostStatusListResponse = array of PostStatus` - `id: string` Unique identifier - `color: string` Color for UI display - `isDefault: boolean` Whether this is the default status for new posts - `name: string` Display name - `object: "post_status"` Object type identifier - `"post_status"` - `type: "reviewing" or "unstarted" or "active" or 2 more` The workflow stage this status represents - `"reviewing"` - `"unstarted"` - `"active"` - `"completed"` - `"canceled"` # 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` # Custom Fields ## List custom fields **get** `/v2/custom_fields` Returns all custom fields configured in your organization. This endpoint returns all custom fields at once (typically a small list). No pagination is supported. ### Response Format Returns a list object with: - `object` - Always "list" - `data` - Array of custom field objects - `nextCursor` - Always null ### Custom Field Object Each custom field includes: - `id` - Unique field identifier - `label` - Field label displayed to users - `type` - Field type (text, number, select, multi-select, checkbox, date) - `required` - Whether the field is required - `placeholder` - Placeholder text (for text/number fields) - `public` - Whether the field value is publicly visible - `internal` - Whether the field is for internal use only - `options` - Array of options (for select/multi-select fields) ### Field Types - `text` - Single line text input - `number` - Numeric input - `select` - Single-choice dropdown - `multi-select` - Multiple-choice dropdown - `checkbox` - Boolean checkbox - `date` - Date picker ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Returns - `CustomFieldList object { data, nextCursor, object, pagination }` - `data: array of CustomField` Array of custom fields - `id: string` Unique identifier - `label: string` Field label displayed to users - `object: "custom_field"` Object type identifier - `"custom_field"` - `type: "text" or "number" or "select" or 4 more` Field type - `"text"` - `"number"` - `"select"` - `"multi-select"` - `"checkbox"` - `"date"` - `"file"` - `allowMultiple: optional boolean` Whether multiple files can be uploaded (file fields only) - `createdAt: optional string` ISO timestamp when created - `internal: optional boolean` Whether the field is for internal use only - `options: optional array of object { id, label }` Options for select/multi-select fields - `id: string` Option unique identifier - `label: string` Option display label - `placeholder: optional string` Placeholder text - `public: optional boolean` Whether the field value is publicly visible - `required: optional boolean` Whether the field is required - `updatedAt: optional string` ISO timestamp when last updated - `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/custom_fields \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "data": [ { "id": "65d26304b2e65b1e1278170c", "label": "Your @username", "object": "custom_field", "type": "text", "allowMultiple": false, "createdAt": "2025-04-06T14:11:58.141Z", "internal": false, "options": [ { "id": "65d26304b2e65b1e1278170d", "label": "High Priority" } ], "placeholder": "Enter your username", "public": false, "required": true, "updatedAt": "2025-04-06T14:11:58.141Z" } ], "nextCursor": null, "object": "list", "pagination": { "limit": 10, "page": 1, "total": 42, "totalPages": 5 } } ``` ## Get a custom field by ID **get** `/v2/custom_fields/{id}` Retrieves a single custom field by its unique identifier. Returns the custom field object if found in your organization. ### Response Returns a custom field object with: - `id` - Unique field identifier - `label` - Field label displayed to users - `type` - Field type (text, number, select, multi-select, checkbox, date) - `required` - Whether the field is required - `placeholder` - Placeholder text (for text/number fields) - `public` - Whether the field value is publicly visible - `internal` - Whether the field is for internal use only - `options` - Array of options (for select/multi-select fields) ### Errors - `404` - Custom field not found in your organization ### Path Parameters - `id: string` Custom field 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 - `CustomField object { id, label, object, 9 more }` - `id: string` Unique identifier - `label: string` Field label displayed to users - `object: "custom_field"` Object type identifier - `"custom_field"` - `type: "text" or "number" or "select" or 4 more` Field type - `"text"` - `"number"` - `"select"` - `"multi-select"` - `"checkbox"` - `"date"` - `"file"` - `allowMultiple: optional boolean` Whether multiple files can be uploaded (file fields only) - `createdAt: optional string` ISO timestamp when created - `internal: optional boolean` Whether the field is for internal use only - `options: optional array of object { id, label }` Options for select/multi-select fields - `id: string` Option unique identifier - `label: string` Option display label - `placeholder: optional string` Placeholder text - `public: optional boolean` Whether the field value is publicly visible - `required: optional boolean` Whether the field is required - `updatedAt: optional string` ISO timestamp when last updated ### Example ```http curl https://do.featurebase.app/v2/custom_fields/$ID \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" ``` #### Response ```json { "id": "65d26304b2e65b1e1278170c", "label": "Your @username", "object": "custom_field", "type": "text", "allowMultiple": false, "createdAt": "2025-04-06T14:11:58.141Z", "internal": false, "options": [ { "id": "65d26304b2e65b1e1278170d", "label": "High Priority" } ], "placeholder": "Enter your username", "public": false, "required": true, "updatedAt": "2025-04-06T14:11:58.141Z" } ``` ## Domain Types ### Custom Field - `CustomField object { id, label, object, 9 more }` - `id: string` Unique identifier - `label: string` Field label displayed to users - `object: "custom_field"` Object type identifier - `"custom_field"` - `type: "text" or "number" or "select" or 4 more` Field type - `"text"` - `"number"` - `"select"` - `"multi-select"` - `"checkbox"` - `"date"` - `"file"` - `allowMultiple: optional boolean` Whether multiple files can be uploaded (file fields only) - `createdAt: optional string` ISO timestamp when created - `internal: optional boolean` Whether the field is for internal use only - `options: optional array of object { id, label }` Options for select/multi-select fields - `id: string` Option unique identifier - `label: string` Option display label - `placeholder: optional string` Placeholder text - `public: optional boolean` Whether the field value is publicly visible - `required: optional boolean` Whether the field is required - `updatedAt: optional string` ISO timestamp when last updated ### Custom Field List - `CustomFieldList object { data, nextCursor, object, pagination }` - `data: array of CustomField` Array of custom fields - `id: string` Unique identifier - `label: string` Field label displayed to users - `object: "custom_field"` Object type identifier - `"custom_field"` - `type: "text" or "number" or "select" or 4 more` Field type - `"text"` - `"number"` - `"select"` - `"multi-select"` - `"checkbox"` - `"date"` - `"file"` - `allowMultiple: optional boolean` Whether multiple files can be uploaded (file fields only) - `createdAt: optional string` ISO timestamp when created - `internal: optional boolean` Whether the field is for internal use only - `options: optional array of object { id, label }` Options for select/multi-select fields - `id: string` Option unique identifier - `label: string` Option display label - `placeholder: optional string` Placeholder text - `public: optional boolean` Whether the field value is publicly visible - `required: optional boolean` Whether the field is required - `updatedAt: optional string` ISO timestamp when last updated - `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