Create the status in a space (roadmap)
POST
https://forumulate.com/api /orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status Moves an item to another lane. message (Markdown) is the team’s answer: it is kept in the item’s history, shown under its title as the official response, and sent with the move to every voter and watcher. Every move is recorded on the item.
Requires token scope: write (the coarse method gate) for Atlas key (ak_), Personal Access Token (cft_) or OAuth (cfo_) auth — in addition to any catalog scope (GET /pats/scopes) the route’s capability needs. Session/cookie auth is governed by RBAC capabilities instead.
Parameters
orgIdstringpathrequiredtopicIdstringpathrequiredforumulate-versionstringheaderAPI version negotiation. Send
forumulate-version: 2026-12-01 on every request from a new integration; it is optional only so that integrations written before versioning keep working. Each version includes everything before it:Idempotency-KeystringheaderMakes this write safe to retry. Optional — send any unique string (a UUID is ideal) and the first request runs while its response is remembered under that key for 24 hours; an identical retry replays that same status and body instead of acting twice, so a timeout or a 5xx from the edge can be retried without creating a second row. A retry sent while the first request is still in flight is refused with
409. The key is scoped to the method and path, so the same key on a different endpoint is a different request. Error responses of 5xx are never cached — those retries re-run. A key is bound to the BODY it was first used with: reusing it with a different body is a 409 idempotency.key_reused, not a replay, because replaying would tell you a write landed that never ran. Generate one key per request, never per process or per session.spaceIdstringpathrequiredThe space: its uuid, or its slug within the organization. It is not decoration on a nested route — an id further down the path that belongs to a DIFFERENT space is a 404, not a cross-space read, which is why the operations that never bound this parameter still had to declare it.
Request body application/json
statusstringrequiredmessagestring{
"status": "string",
"message": "string"
}Code samples
cURL
curl -X POST "https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status" \
-H "forumulate-version: <value>" \
-H "Idempotency-Key: <value>" \
-H "Content-Type: application/json" \
-d '{"status":"string","message":"string"}'TypeScript
const res = await fetch('https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status', {
method: 'POST',
headers: {
'forumulate-version': '<value>',
'Idempotency-Key': '<value>',
'Content-Type': 'application/json',
},
body: JSON.stringify({"status":"string","message":"string"}),
});
const data = await res.json();Python
import requests
res = requests.request(
"POST",
"https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status",
headers={
"forumulate-version": "<value>",
"Idempotency-Key": "<value>",
"Content-Type": "application/json",
},
json={"status": "string", "message": "string"},
)
data = res.json()Go
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
req, _ := http.NewRequest("POST", "https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status", strings.NewReader(`{"status":"string","message":"string"}`))
req.Header.Set("forumulate-version", "<value>")
req.Header.Set("Idempotency-Key", "<value>")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}PHP
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['forumulate-version: <value>', 'Idempotency-Key: <value>', 'Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"status":"string","message":"string"}');
$response = curl_exec($ch);
curl_close($ch);
echo $response;Ruby
require 'net/http'
require 'uri'
uri = URI('https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status')
req = Net::HTTP::Post.new(uri)
req['forumulate-version'] = '<value>'
req['Idempotency-Key'] = '<value>'
req['Content-Type'] = 'application/json'
req.body = '{"status":"string","message":"string"}'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts res.bodyJava
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpRequest.BodyPublishers;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/roadmap/{topicId}/status"))
.header("forumulate-version", "<value>")
.header("Idempotency-Key", "<value>")
.header("Content-Type", "application/json")
.method("POST", BodyPublishers.ofString("{\"status\":\"string\",\"message\":\"string\"}"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());Responses
200 Success.
Example
{
"item": {
"answerCount": 0,
"answerPostId": "string",
"assignedGroupId": "string",
"assigneeId": "string",
"authorAvatarSeed": "string",
"authorAvatarUrl": "string",
"authorId": "string",
"authorKind": "anonymous",
"authorName": "string",
"authorPoints": 0,
"authors": [
{
"name": "string",
"userId": "string"
}
],
"authorTitle": "string",
"authorUsername": "string",
"bookmarked": true,
"byline": {
"avatarUrl": "string",
"name": "string",
"url": "string"
},
"bylines": [
{
"author": null,
"avatarUrl": null,
"bio": null,
"handle": "string",
"name": "string",
"role": null,
"url": null,
"userId": null
}
],
"categoryId": "string",
"commentsEnabled": true,
"contentChangedAt": "2024-01-01T00:00:00Z",
"coverUrl": "string",
"customization": {
"customCss": "string",
"jsonLd": "string",
"ogDescription": "string",
"ogTitle": "string",
"shareImageUrl": "string",
"theme": {
"accentColor": null,
"bodyFontFamily": null,
"changelog": null,
"colorScheme": null,
"components": null,
"contentWidth": null,
"customFontFamily": null,
"font": null,
"headingFontFamily": null,
"layout": null,
"monoFontFamily": null,
"motion": null,
"palette": null,
"paletteDark": null,
"post": null,
"primaryColor": null,
"typography": null
},
"twitterCard": "summary"
},
"deletedAt": "2024-01-01T00:00:00Z",
"excerpt": "string",
"expiresAt": "2024-01-01T00:00:00Z",
"featured": true,
"featuredLink": "string",
"fields": {},
"firstImageUrl": "string",
"furtherReading": [
{
"href": "string",
"label": "string"
}
],
"id": "string",
"isNew": true,
"kind": "ARTICLE",
"lastActivityAt": "2024-01-01T00:00:00Z",
"lastPostedAt": "string",
"lastPosterName": "string",
"likeCount": 0,
"linkedTopics": [
{
"createdAt": "2024-01-01T00:00:00Z",
"fromPostNumber": 0,
"id": "string",
"slug": "string",
"title": "string"
}
],
"listed": true,
"mergedIntoId": "string",
"number": 0,
"onThisPage": "always",
"op": {
"actionCode": "string",
"actionData": {},
"authorAvatarSeed": "string",
"authorAvatarUrl": "string",
"authorBadges": [
{
"icon": null,
"name": null,
"slug": null
}
],
"authorBadgeTiers": {
"bronze": 0,
"gold": 0,
"silver": 0
},
"authorFlair": {
"bgColor": null,
"color": null,
"icon": null,
"label": "string"
},
"authorId": "string",
"authorKind": "anonymous",
"authorName": "string",
"authorPoints": 0,
"authorRole": "moderator",
"authorStatus": {
"emoji": null,
"message": null
},
"authorTitle": "string",
"authorTrust": 0,
"authorUsername": "string",
"content": {
"type": "doc",
"content": [
{
"type": "heading",
"attrs": {
"level": 1
},
"content": [
{
"type": "text",
"text": "Getting started"
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Install the SDK, then create your first page."
}
]
},
{
"type": "codeBlock",
"attrs": {
"language": "bash"
},
"content": [
{
"type": "text",
"text": "npm i @forumulate/sdk"
}
]
}
]
},
"createdAt": "2024-01-01T00:00:00Z",
"editedAt": "2024-01-01T00:00:00Z",
"flagCount": 0,
"id": "string",
"isOp": true,
"likeCount": 0,
"parentAuthorName": "string",
"parentPostId": "string",
"parentPostNumber": 0,
"postNumber": 0,
"reactions": {},
"status": "hidden",
"voteCount": 0,
"whisper": true,
"wiki": true
},
"pinned": true,
"pinnedGlobally": true,
"pinnedUntil": "2024-01-01T00:00:00Z",
"posters": [
{
"avatarSeed": "string",
"avatarUrl": null,
"kind": "anonymous",
"name": "string",
"role": "frequent",
"username": null
}
],
"promotedPage": {
"id": "string",
"spaceId": "string",
"title": "string"
},
"promotedPageId": "string",
"publishedAt": "2024-01-01T00:00:00Z",
"readingMinutes": 0,
"releaseVersion": "string",
"replies": [
{
"actionCode": null,
"actionData": null,
"authorAvatarSeed": "string",
"authorAvatarUrl": "string",
"authorBadges": [
null
],
"authorBadgeTiers": {
"bronze": null,
"gold": null,
"silver": null
},
"authorFlair": {
"bgColor": null,
"color": null,
"icon": null,
"label": null
},
"authorId": null,
"authorKind": "anonymous",
"authorName": "string",
"authorPoints": 0,
"authorRole": "moderator",
"authorStatus": {
"emoji": null,
"message": null
},
"authorTitle": "string",
"authorTrust": 0,
"authorUsername": "string",
"content": {
"type": "doc",
"content": [
{
"type": "heading",
"attrs": {
"level": 1
},
"content": [
{
"type": "text",
"text": "Getting started"
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Install the SDK, then create your first page."
}
]
},
{
"type": "codeBlock",
"attrs": {
"language": "bash"
},
"content": [
{
"type": "text",
"text": "npm i @forumulate/sdk"
}
]
}
]
},
"createdAt": "2024-01-01T00:00:00Z",
"editedAt": null,
"flagCount": 0,
"id": "string",
"isOp": true,
"likeCount": 0,
"parentAuthorName": "string",
"parentPostId": null,
"parentPostNumber": 0,
"postNumber": 0,
"reactions": {},
"status": "hidden",
"voteCount": 0,
"whisper": true,
"wiki": true
}
],
"replyCount": 0,
"replyPreviews": [
{
"author": "string",
"authorAvatar": null,
"authorSeed": "string",
"text": "string"
}
],
"review": {
"note": "string",
"requestedAt": "2024-01-01T00:00:00Z",
"requestedBy": "string",
"reviewedAt": "2024-01-01T00:00:00Z",
"reviewedBy": "string",
"state": "approved"
},
"roadmapStatus": "string",
"scheduledFor": "2024-01-01T00:00:00Z",
"seo": {
"noindex": true,
"seoDescription": "string",
"seoTitle": "string"
},
"series": "string",
"shippedTopicIds": [
"string"
],
"slowModeSeconds": 0,
"slug": "string",
"state": "ARCHIVED",
"status": "archived",
"subtitle": "string",
"tagInfo": [
{
"color": null,
"colorDark": null,
"coverUrl": null,
"description": null,
"gradient": null,
"icon": "string",
"label": "string",
"pagePalette": null,
"pageScheme": "dark",
"slug": "string",
"textColor": null,
"textColorDark": null
}
],
"tags": [
"string"
],
"title": "string",
"unread": 0,
"updatedAt": "2024-01-01T00:00:00Z",
"viewCount": 0,
"voteCount": 0
},
"warnings": [
"tags: \"Launch Day\" is stored as \"launch-day\""
]
}Schema
itemobjectrequiredShow child parameters
answerCountnumberanswerPostIdstringrequiredassignedGroupIdstringrequiredassigneeIdstringrequiredauthorAvatarSeedstringauthorAvatarUrlstringauthorIdstringrequiredauthorKindstringauthorNamestringrequiredauthorPointsnumberauthorsobject[]requiredShow child parameters
namestringrequireduserIdstringrequiredauthorTitlestringauthorUsernamestringbookmarkedbooleanbylineBylinerequiredShow child parameters
avatarUrlstringnamestringurlstringbylinesBylineView[]Show child parameters
authorstringrequiredavatarUrlstringrequiredbiostringrequiredhandlestringnamestringrequiredrolestringrequiredurlstringrequireduserIdstringrequiredcategoryIdstringrequiredcommentsEnabledbooleanrequiredcontentChangedAtstringcoverUrlstringrequiredcustomizationPieceCustomizationrequiredShow child parameters
customCssstringjsonLdstringogDescriptionstringogTitlestringshareImageUrlstringthemeobjecttwitterCardstringdeletedAtstringexcerptstringrequiredexpiresAtstringfeaturedbooleanrequiredfeaturedLinkstringrequiredfieldsobjectrequiredfirstImageUrlstringfurtherReadingobject[]requiredShow child parameters
hrefstringrequiredlabelstringrequiredidstringrequiredisNewbooleankindstringrequiredlastActivityAtstringrequiredlastPostedAtstringlastPosterNamestringlikeCountnumberlinkedTopicsLinkedTopic[]requiredShow child parameters
createdAtstringrequiredfromPostNumbernumberrequiredidstringrequiredslugstringrequiredtitlestringrequiredlistedbooleanrequiredmergedIntoIdstringrequirednumbernumberrequiredonThisPagestringrequiredopobjectrequiredShow child parameters
actionCodestringrequiredactionDataobjectrequiredauthorAvatarSeedstringauthorAvatarUrlstringauthorBadgesobject[]authorBadgeTiersobjectauthorFlairobjectauthorIdstringrequiredauthorKindstringauthorNamestringrequiredauthorPointsnumberauthorRolestringauthorStatusobjectauthorTitlestringauthorTrustnumberrequiredauthorUsernamestringcontentobjectrequiredA page as a block document: TipTap/ProseMirror JSON, the canonical content model shared by the editor, the reader and the Markdown converters. Send this as
content when creating a page or on PUT /pages/{pageId}/content. It is the shape GET /pages/{pageId}/markdown round-trips from. Author in Markdown instead by sending body (whole page) or sections (Markdown per named slot) — use exactly ONE of the three. Every block has a Markdown spelling: :::name{attrs} directives for callouts, tabs, cards, steppers, columns and buttons, listed at GET /docs/markdown-dialect.createdAtstringrequirededitedAtstringrequiredflagCountnumberrequiredidstringrequiredisOpbooleanrequiredlikeCountnumberparentAuthorNamestringparentPostIdstringrequiredparentPostNumbernumberpostNumbernumberrequiredreactionsobjectrequiredstatusstringrequiredvoteCountnumberrequiredwhisperbooleanrequiredwikibooleanrequiredpinnedbooleanrequiredpinnedGloballybooleanrequiredpinnedUntilstringrequiredpostersForumPoster[]Show child parameters
avatarSeedstringrequiredavatarUrlstringrequiredkindstringrequirednamestringrequiredrolestringrequiredusernamestringrequiredpromotedPageobjectrequiredShow child parameters
idstringrequiredspaceIdstringrequiredtitlestringrequiredpromotedPageIdstringrequiredpublishedAtstringrequiredreadingMinutesnumberrequiredreleaseVersionstringrequiredrepliesPostSummary[]requiredShow child parameters
actionCodestringrequiredactionDataobjectrequiredauthorAvatarSeedstringauthorAvatarUrlstringauthorBadgesobject[]authorBadgeTiersobjectauthorFlairobjectauthorIdstringrequiredauthorKindstringauthorNamestringrequiredauthorPointsnumberauthorRolestringauthorStatusobjectauthorTitlestringauthorTrustnumberrequiredauthorUsernamestringcontentobjectrequiredA page as a block document: TipTap/ProseMirror JSON, the canonical content model shared by the editor, the reader and the Markdown converters. Send this as
content when creating a page or on PUT /pages/{pageId}/content. It is the shape GET /pages/{pageId}/markdown round-trips from. Author in Markdown instead by sending body (whole page) or sections (Markdown per named slot) — use exactly ONE of the three. Every block has a Markdown spelling: :::name{attrs} directives for callouts, tabs, cards, steppers, columns and buttons, listed at GET /docs/markdown-dialect.createdAtstringrequirededitedAtstringrequiredflagCountnumberrequiredidstringrequiredisOpbooleanrequiredlikeCountnumberparentAuthorNamestringparentPostIdstringrequiredparentPostNumbernumberpostNumbernumberrequiredreactionsobjectrequiredstatusstringrequiredvoteCountnumberrequiredwhisperbooleanrequiredwikibooleanrequiredreplyCountnumberrequiredreplyPreviewsReplyPreview[]Show child parameters
authorstringrequiredauthorAvatarstringrequiredauthorSeedstringrequiredtextstringrequiredreviewobjectShow child parameters
notestringrequiredrequestedAtstringrequiredrequestedBystringrequiredreviewedAtstringrequiredreviewedBystringrequiredstatestringrequiredroadmapStatusstringrequiredscheduledForstringrequiredseoobjectrequiredShow child parameters
noindexbooleanseoDescriptionstringseoTitlestringseriesstringrequiredshippedTopicIdsstring[]requiredslowModeSecondsnumberslugstringrequiredstatestringrequiredstatusstringrequiredsubtitlestringtagInfoTagDisplay[]Show child parameters
colorstringrequiredcolorDarkstringrequiredcoverUrlstringrequireddescriptionstringrequiredgradientarrayrequirediconstringlabelstringrequiredpagePaletteobjectrequiredpageSchemestringrequiredslugstringrequiredtextColorstringrequiredtextColorDarkstringrequiredtagsstring[]requiredtitlestringrequiredunreadnumberupdatedAtstringrequiredviewCountnumberrequiredvoteCountnumberrequired400 The request could not be understood — a malformed header, path or query value (
request.malformed). A value that was understood but is not acceptable is a 422 validation.failed.{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}401 No credential, or one that is expired or revoked (
auth.required). Send a Bearer token or the session cookie.{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}403 Authenticated, and not allowed — always
rbac.forbidden, whether what is missing is the capability, the token scope, or a resource the token is pinned away from. The message names the capability and, for a scope refusal, the scopes that would grant it. A refusal because of the PLAN is a 402 with quota.exceeded, not this.{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}404 No such thing — one of the ids in the path does not resolve, or resolves to something this caller may not see (
*.not_found).{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}422 The body was understood and a value is unacceptable (
validation.failed). error.details names the field paths.{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}429 Rate limited — see the RateLimit-* headers.
{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}default Error — the stable { error: { code, message } } envelope.
{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}The Forumulate teamUpdated