Create a new post
Creates a new post (feedback submission) in the specified board.
Required Fields
title- Post title (minimum 2 characters). Required unlessintakeModeisfeedback; see “Intake mode” below.
Optional Fields
boardId- Board ID to create the post in. Omit to use the organization’s default board.intakeMode-request(default) orfeedback; see “Intake mode”.source-{ channel, externalId, url?, conversationId?, label? }; see “Provenance and idempotency”.attachTo- An open request this post belongs with; see “attachTo, or link-insight?”.internal- Store on the hidden internal board (never portal-visible); not withboardId.content- Post content in HTML formattags- Array of tag names to attachstatusId- 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 pairseta- Estimated completion date (Unix timestamp or ISO date)assigneeId- Admin ID to assign this post tovisibility- Post-level restriction: ‘public’, ‘authorOnly’ (author and admins) or ‘companyOnly’ (the author’s company). Board and organization access controls still apply.
Author Attribution
For posts created on behalf of users, use the author object:
id- Featurebase user IDuserId- External SSO user IDemail- User’s email addressname- Display nameprofilePicture- Profile picture URL
Resolution priority: id > userId > email > authenticated user
Intake mode
intakeMode is the caller’s declared intent and the ONE field that selects how the post is
processed; author and source only describe where the text came from.
request(default): a finished request, stored exactly as supplied — no AI extraction, no rewrite. Featurebase may still link existing customer evidence TO it in the background.feedback: raw customer feedback (a Slack message, a call note, a survey answer). Processed like a portal post: organized into requests when the workspace’s “Organize submissions” lane is on, otherwise its claims are extracted and matched against existing requests. The workspace’s Autopilot dial, plan, AI budget, moderation and spam settings apply, also when the post is filed under the API key’s own user.statusIdandetaare rejected (400). Feedback creates are rate-limited per workspace more strictly than requests (429 withRetry-After).
In feedback mode title is optional: omit it and the first line of content becomes the title,
cut to 120 characters; content is then required (400 on content when both are empty). In
request mode title stays required, minimum 2 characters.
At most 5 requests organized from one submission notify the team (admin notifications, mentions,
Slack, Discord, tracker pushes); the rest are created silently, still returned in
processing.results and still fire post.created.
Length limits for feedback
Text is read whole or rejected (400 on content; the message states the limit and the length
sent). The count is plain text: title plus content without HTML. The limit follows
source.channel: call 120,000 (a two-hour transcript), email and api 60,000, slack and
discord 30,000; no channel means api. Workspaces with the AI switched off have no limit. To send
more, split the text and give each part its own source.externalId.
Long text is read in parts, then each ask is matched and filed: seconds for a message, tens of
minutes for a two-hour transcript. A repeated ask is reported once. One submission yields at most
30 requests: the 29 strongest, plus one held request listing the rest. Without the organize lane,
at most 50 asks are captured. At most 20 submissions per workspace per hour may exceed one reading
pass (about 16,000 characters with the organize lane, otherwise 6,000, or 24,000 for a call);
past that, 429 with Retry-After.
The response reports intakeMode and processing: status is queued (an AI run was
enqueued; its result lands on the post later), skipped with a reason (request_mode,
autopilot_off, pipeline_paused, support_board, spam_held, staff_authored, …), or
existing on an idempotent replay. Fetch GET /v2/posts/{id} to read where the feedback ended up.
Provenance and idempotency
source records where a request came from and makes the create idempotent: sending the same
(channel, externalId) twice returns the first post unchanged, with deduped: true. Ids are
stored as api:<externalId>, so they never collide with Featurebase’s own. The channels
feedback, widget and support are reserved for the portal, the widget and the inbox (400).
With source and no author, the post is attributed to a guest named after source.label (or
the channel): a relayed request belongs to the customer who said it, not to the API key.
attachTo, or link-insight?
attachTo records that the NEW post belongs with an existing request. The post stays a post: it
keeps kind: 'issue', stays in GET /v2/posts, keeps its own votes, its author does not become a
supporter of the target, and it is not listed by GET /v2/posts/{targetId}/insights. The link is
silent: no notification, no ack.
POST /v2/posts/{id}/link-insight files the post as a quote under the request instead: it
becomes an insight, leaves the posts resource (GET /v2/posts/{id} answers 404), appears in
GET /v2/posts/{targetId}/insights, and its author counts as a supporter.
Two customers asking for the same thing who each keep their own request → attachTo. A sentence
that is evidence for a request you already track → create the post, then link-insight. Both take
an open request in this workspace as target.
attachTo cannot be combined with intakeMode: 'feedback' (400 invalid_parameter on
attachTo): one says where the text belongs, the other asks Featurebase to decide. The link is
made AFTER the post is written, so an unusable target is a 422 that says the post was created —
not rolled back. The target is refused when it is missing in this workspace, not a request, merged,
a processed submission, awaiting moderation, held as spam, or the new post itself.
Backdating (Imports)
createdAt- Override creation date for importing historical data
Response
- 201 - created. The post object, plus
intakeModeandprocessing. - 200 - a post already existed for this
(source.channel, source.externalId): that post, unchanged, withdeduped: trueandprocessing.status: 'existing'; a replay never links anything a second time. - 422 -
attachTonamed a request that cannot carry evidence. The post was still created; the error message says so.
Body ParametersJSON
Id of an existing open request this post is evidence for. The post is created, then linked.
Board ID to create the post in. Omit to use the default board of the organization.
Post content (HTML). Required when intakeMode is ‘feedback’ and no title is given. In ‘feedback’ mode its plain text is limited by source.channel (see intakeMode); longer content is rejected with a 400.
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).
When true, hides the issue from portal/public surfaces. Omitted or false is visible.
Post title. Required unless intakeMode is ‘feedback’, where raw customer text rarely has one: omit it (or send it blank) and the first line of content becomes the title, cut to 120 characters.
Create a new post
curl https://do.featurebase.app/v2/posts \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $FEATUREBASE_API_KEY" \
-d '{
"assigneeId": "507f1f77bcf86cd799439013",
"attachTo": "507f1f77bcf86cd799439014",
"boardId": "507f1f77bcf86cd799439011",
"commentsEnabled": true,
"content": "<p>It would be great to have dark mode.</p>",
"createdAt": "2025-01-15T10:30:00.000Z",
"customFields": {
"507f1f77bcf86cd799439011": "high"
},
"eta": "2025-12-31T23:59:59.000Z",
"intakeMode": "feedback",
"internal": true,
"notifyAdmins": true,
"portalHidden": true,
"statusId": "507f1f77bcf86cd799439012",
"tags": [
"feature",
"ui"
],
"title": "Add dark mode support",
"upvotes": 5,
"visibility": "public"
}'{
"id": "507f1f77bcf86cd799439011",
"access": {
"companyExternalIds": [
"string"
],
"userIds": [
"string"
]
},
"anchor": {
"commentId": "commentId",
"conversationId": "conversationId",
"messageId": "messageId",
"ranges": [
{
"end": 0,
"start": 0
}
],
"sourceRecordId": "sourceRecordId",
"submissionId": "submissionId"
},
"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": "<p>It would be great to have a dark mode option for the dashboard.</p>",
"createdAt": "2023-12-12T00:00:00.000Z",
"customFields": {
"cf_priority": "bar",
"cf_effort": "bar"
},
"eta": "2025-01-01T00:00:00.000Z",
"features": {
"commentsEnabled": true
},
"groupKey": null,
"inReview": false,
"insightSource": {
"channel": "slack",
"capturedBy": "manual",
"conversationId": "conversationId",
"externalId": "externalId",
"sourceLabel": "sourceLabel",
"url": "url"
},
"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,
"kind": "issue",
"linkedInsightCount": 0,
"linkedIssueId": null,
"object": "post",
"opportunityAmount": 30000,
"portalHidden": false,
"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,
"deduped": true,
"intakeMode": "request",
"processing": {
"status": "queued",
"reason": "request_mode",
"results": [
{
"outcome": "request",
"title": "Export the board to CSV",
"postId": "507f1f77bcf86cd799439011",
"postTitle": "CSV export",
"reason": "too_many_topics"
}
]
}
}Returns Examples
{
"id": "507f1f77bcf86cd799439011",
"access": {
"companyExternalIds": [
"string"
],
"userIds": [
"string"
]
},
"anchor": {
"commentId": "commentId",
"conversationId": "conversationId",
"messageId": "messageId",
"ranges": [
{
"end": 0,
"start": 0
}
],
"sourceRecordId": "sourceRecordId",
"submissionId": "submissionId"
},
"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": "<p>It would be great to have a dark mode option for the dashboard.</p>",
"createdAt": "2023-12-12T00:00:00.000Z",
"customFields": {
"cf_priority": "bar",
"cf_effort": "bar"
},
"eta": "2025-01-01T00:00:00.000Z",
"features": {
"commentsEnabled": true
},
"groupKey": null,
"inReview": false,
"insightSource": {
"channel": "slack",
"capturedBy": "manual",
"conversationId": "conversationId",
"externalId": "externalId",
"sourceLabel": "sourceLabel",
"url": "url"
},
"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,
"kind": "issue",
"linkedInsightCount": 0,
"linkedIssueId": null,
"object": "post",
"opportunityAmount": 30000,
"portalHidden": false,
"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,
"deduped": true,
"intakeMode": "request",
"processing": {
"status": "queued",
"reason": "request_mode",
"results": [
{
"outcome": "request",
"title": "Export the board to CSV",
"postId": "507f1f77bcf86cd799439011",
"postTitle": "CSV export",
"reason": "too_many_topics"
}
]
}
}