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; always true forsource.channel: 'call')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, and it is not listed by
GET /v2/posts/{targetId}/insights. The link is silent: no notification, no ack. Its author does
not become a supporter of the target when the post is created. A held post's author (inReview)
becomes a supporter (voter and subscriber) of the target when a member approves it, if they can open
the target; source.channel: 'call' posts are always held.
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
Id of an existing open request this post is evidence for. The post is created, then linked. A held post's author (inReview) becomes a supporter (voter and subscriber) of the target when a member approves it, if they can open the target; source.channel: 'call' posts are always held.
507f1f77bcf86cd799439014Author to attribute the post to. If not provided, uses the authenticated user — unless source is given, in which case a guest author is synthesised from source.label (or the channel name), because a relayed request belongs to the customer who said it. Supports multiple identification methods: id (Featurebase ID), userId (external SSO ID), or email.
Board ID to create the post in. Omit to use the default board of the organization.
507f1f77bcf86cd799439011Post 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.
<p>It would be great to have dark mode.</p>Custom field values keyed by field ID (ObjectId). Send each value in the form its field type takes: text: a string; number: a number or a numeric string ("5"); checkbox: true/false or "true"/"false"; date: an ISO 8601 string; select: an option label or id; multi-select: an option label or id, or an array of them. null clears a field. A value the field type cannot take is rejected with a 400.
Whether post is pending moderation. Ignored for source.channel: 'call': a call is always created pending moderation (inReview: true).
falseWhat the text is. 'request' (default): a finished request — stored exactly as supplied, no AI claim extraction, regardless of author. 'feedback': raw customer feedback — processed like a portal post (organized into a request, or its claims extracted and matched against existing requests) under the workspace's Autopilot dial, plan, AI budget and moderation settings. In 'feedback' mode title is optional (the first line of content becomes it), statusId and eta are rejected (400) because raw feedback has no decision yet, and text is read in full up to the limit of its source.channel — 120,000 characters of plain text for 'call' (a two-hour transcript), 60,000 for 'email' and 'api', 30,000 for 'slack' and 'discord'; without a source.channel the 'api' limit applies — while anything longer is rejected (400) rather than trimmed. Long text takes tens of minutes rather than seconds to process, and one submission yields at most 30 requests.
request, feedbackfeedbackPush 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.
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).
trueWhen true, hides the issue from portal/public surfaces. Omitted or false is visible. Ignored for source.channel: 'call': a call is always created hidden (portalHidden: true).
trueProvenance of this request, and the idempotency key for the create. With source and no author, the post is attributed to a guest named after label (or the channel) so it belongs to the customer who said it, not to the API key.
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.
2 - 512Add dark mode supportInitial upvotes count. Defaults to 1 (post author is automatically added as voter). Use 0 to create a post without any votes.
0 <= x5Response
Created
When kind is 'insight', where exactly the insight points back into its origin: an insight source record with character ranges into its fullText, or the native conversation/message/comment/post ids.
ID of the admin assigned to this post, null if unassigned
507f1f77bcf86cd799439013Post content in HTML format
<p>It would be great to have a dark mode option for the dashboard.</p>Present and true only on POST /v2/posts, when the request carried a source.externalId that already had a post. The existing post is returned unchanged with HTTP 200; a newly created post returns HTTP 201 without this field.
truetrueEstimated completion time as ISO 8601 timestamp, null if not set
2025-01-01T00:00:00.000ZWhen kind is 'insight', the triage grouping key (source record id, conversation id, origin post id, or the insight's own id for singletons). Legacy insights may be null and group as singletons.
Provenance of an insight: which channel it came from and how it was captured.
Present only on POST /v2/posts: the intakeMode the post was processed under ('request' when the request named none). On an idempotent replay (deduped: true) this is the mode the post was ORIGINALLY created with.
request, feedbackrequestDiscriminates an actionable work item ('issue') from a customer submission whose claims were extracted into insights ('record' — not a work item). Defaults to 'issue' for all pre-existing posts. Default list responses return issues only; pass kind='record' to opt in. Raw signal ('insight') is never returned by the posts resource — insights are served by /v2/insights.
issue, insight, recordissueNumber of insights linked to this issue as supporting evidence. Only meaningful when kind is 'issue'.
0When kind is 'insight', the ID of the issue this insight supports. Null when the insight is unlinked or when kind is 'issue'.
Total opportunity amount from linked HubSpot deals and Salesforce opportunities
30000True when the issue is hidden from portal/public surfaces. Missing stored values are returned as false.
falseFull URL to view the post
https://feedback.example.com/p/add-dark-mode-supportOn POST /v2/posts — queued: a processing run (claim extraction or the Organize rewrite) was enqueued and its result lands asynchronously on the post. skipped: nothing was enqueued; reason says which gate decided ('request_mode' for every intakeMode: 'request' create). existing: the create was an idempotent replay and the post was not processed again. On GET /v2/posts/{id} this field is present only for posts created with intakeMode: 'feedback' and reports how far that processing has got ('queued', 'processing', 'complete', 'needs_review', or 'skipped' with the same reason the create returned), with results listing what was made of the submission once the run has finished.