API reference
16 endpoints, served at https://orbitwrite.com/api/v1. Each one lists the credentials it accepts, its parameters and every response it can give.
/api/v1/conversationsGET/api/v1/conversations/countPOST/api/v1/conversations/syncGET/api/v1/conversations/sync-statusPOST/api/v1/conversations/mark-allPOST/api/v1/conversations/snooze-allGET/api/v1/conversations/pending-repliesPOST/api/v1/conversations/pending-replies/{pendingReplyId}/decidePOST/api/v1/conversations/pullGET/api/v1/conversations/{conversationId}PATCH/api/v1/conversations/{conversationId}POST/api/v1/conversations/{conversationId}/assignPOST/api/v1/conversations/{conversationId}/readPOST/api/v1/conversations/{conversationId}/unreadPOST/api/v1/conversations/{conversationId}/snoozePOST/api/v1/conversations/{conversationId}/messagesRequires inbox:read. Reply and mention conversations across the workspace's connected accounts, newest first. ?postId= scopes to one published post row (for an in-composer replies panel) and ?groupId= to a post by its composer group across every channel — one or the other, not both; ?type= selects replies vs mentions, omit it for both. Reads the local store — call POST /conversations/sync to refresh from the platforms. Rows past their platform's retention window (LinkedIn member content) are re-read from the platform first, waiting a few seconds at most; contentExpired stays true on any that couldn't be.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
statusstring"open""resolved""snoozed"platformstring"bluesky""twitter""threads""mastodon""facebook_page""instagram""linkedin_page"typestring"reply""mention"postIdstringConversations under one published post row — the postId on a GET /posts entry or in GET /analytics/results, not the post's groupId. For that, pass groupId.
groupIdstringConversations under a post by its composer group (the id GET /posts and GET /posts/draft use), across every channel it went out on. Not alongside postId.
accountIdstringassigneestringOwnership scope: me (session caller's own), unassigned, or a userId
inboxIdstringScope to one team inbox (Inbox 2.0)
cursorstringlimitintegerBetween 1 and 100.
200ConversationListA page of conversations
conversationsConversationSummary[]RequiredidstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
nextCursorstring | nullRequiredPass as cursor to fetch the next page; null when exhausted
curl -X GET "https://orbitwrite.com/api/v1/conversations" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"conversations": [
{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true
}
],
"nextCursor": "string"
}Requires inbox:read. How many conversations a view holds — what the inbox's badges show. It takes the same filters as GET /conversations (type, assignee, inboxId, platform, accountId, postId or groupId), so a badge counts exactly what selecting that view lists, plus status (open by default). unread=true narrows to conversations whose latest activity is an incoming message nobody has read yet — what the app's sidebar badge counts, which is a different question from how much is still open. A session caller that names no organizationId falls back to its active workspace, reading 0 when it has none.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
platformstring"bluesky""twitter""threads""mastodon""facebook_page""instagram""linkedin_page"typestring"reply""mention"postIdstringConversations under one published post row — the postId on a GET /posts entry or in GET /analytics/results, not the post's groupId. For that, pass groupId.
groupIdstringConversations under a post by its composer group (the id GET /posts and GET /posts/draft use), across every channel it went out on. Not alongside postId.
accountIdstringassigneestringOwnership scope: me (session caller's own), unassigned, or a userId
inboxIdstringScope to one team inbox (Inbox 2.0)
statusstring"open""resolved""snoozed"unreadstringOnly conversations with unread incoming activity
"true""false"200objectThe count
countintegerRequiredcurl -X GET "https://orbitwrite.com/api/v1/conversations/count" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"count": 0
}Requires inbox:update — a sync writes the shared store and spends the platforms' metered read budget. Ingests replies for the workspace's in-scope published posts (X/Threads/Bluesky) through the platform adapters into the inbox store. Backs the "Sync now" button and the on-open refresh. Idempotent — re-running only inserts genuinely new replies. Skipped (with skipped: true) when a sync ran within the last 2 minutes, unless force is set. Each account is also polled no more often than its platform's cadence allows (notDue counts the ones left out; force polls them anyway), and a platform that has rate-limited us is left alone until its reset, even under force (throttled). Forced syncs are rate limited per credential.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
forcebooleanBypass the recent-sync cooldown (used by the explicit "Sync now" button)
200InboxSyncResultSync summary
fetchedintegerRequiredinsertedintegerRequiredperPlatformobjectRequirederrorsobject[]Requiredthrottledobject[]Steps a platform is rate-limiting right now, and until when. GET /conversations/sync-status reads the same state.
notDueintegerAccounts left out because their poll cadence isn't due yet; pass force to poll them anyway
skippedbooleanTrue when the sync was skipped because one ran within the cooldown window; pass force to override
curl -X POST "https://orbitwrite.com/api/v1/conversations/sync" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "string",
"force": true
}'{
"fetched": 0,
"inserted": 0,
"perPlatform": {},
"errors": [
{
"platform": "string",
"accountId": "string",
"message": "string"
}
],
"throttled": [
{
"platform": "string",
"accountId": "string",
"step": "string",
"until": "string"
}
],
"notDue": 0,
"skipped": true
}Requires inbox:read. One entry per connected channel that has an inbox: when it was last pulled, the schedule the worker keeps (mode, pollIntervalSec, nextPollAt) and any steps the platform is rate-limiting right now (throttles, each with the instant it will be asked again). A platform that has rate-limited the application is left alone until its reset, even by a forced sync, so this is how to tell a quiet inbox from a paused one.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200objectPer-channel polling status
accountsInboxSyncStatus[]RequiredaccountIdstringRequiredplatformstringRequireddisplayHandlestring | nullRequiredneedsReconnectbooleanRequiredThe channel needs reconnecting, so it is not being polled at all
lastPolledAtstring | nullRequiredmodestringRequiredActive while something moved in the last day, else idle (polled more slowly)
"active""idle"pollIntervalSecintegerRequiredThe gap between unforced polls in the current mode
nextPollAtstring | nullRequiredThe earliest the next unforced poll runs. Null when the channel has never been polled or is paused for a reconnect
curl -X GET "https://orbitwrite.com/api/v1/conversations/sync-status" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"accounts": [
{
"accountId": "string",
"platform": "string",
"displayHandle": "string",
"needsReconnect": true,
"lastPolledAt": "string",
"mode": "active",
"pollIntervalSec": 0,
"nextPollAt": "string",
"throttles": [
{
"step": "activity",
"until": "string",
"reason": "string"
}
]
}
]
}Requires inbox:update. Flips every conversation currently in from to to for the workspace — backs the list's "Resolve all / Reopen all" action. type, platform, postId or groupId, accountId, assignee and inboxId scope the sweep exactly as they scope GET /conversations, so pass the filters of the view the sweep fires from; omit them all to sweep the whole workspace.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
fromstringRequired"open""resolved""snoozed"platformstring"bluesky""twitter""threads""mastodon""facebook_page""instagram""linkedin_page"typestring"reply""mention"postIdstringConversations under one published post row — the postId on a GET /posts entry or in GET /analytics/results, not the post's groupId. For that, pass groupId.
groupIdstringConversations under a post by its composer group (the id GET /posts and GET /posts/draft use), across every channel it went out on. Not alongside postId.
accountIdstringassigneestringOwnership scope: me (session caller's own), unassigned, or a userId
inboxIdstringScope to one team inbox (Inbox 2.0)
tostringRequired"open""resolved"200objectHow many conversations changed
countintegerRequiredcurl -X POST "https://orbitwrite.com/api/v1/conversations/mark-all" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "open",
"to": "open"
}'{
"count": 0
}Requires inbox:update. Snoozes every conversation currently in from until until, moving them to the shared "snoozed" category — backs the list's "Snooze all" action. A background sweep returns each to the open list (as unread) when that time passes. type, platform, postId or groupId, accountId, assignee and inboxId scope the sweep exactly as they scope GET /conversations; omit them all to sweep the whole workspace.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
fromstringRequired"open""resolved""snoozed"platformstring"bluesky""twitter""threads""mastodon""facebook_page""instagram""linkedin_page"typestring"reply""mention"postIdstringConversations under one published post row — the postId on a GET /posts entry or in GET /analytics/results, not the post's groupId. For that, pass groupId.
groupIdstringConversations under a post by its composer group (the id GET /posts and GET /posts/draft use), across every channel it went out on. Not alongside postId.
accountIdstringassigneestringOwnership scope: me (session caller's own), unassigned, or a userId
inboxIdstringScope to one team inbox (Inbox 2.0)
untilstringRequiredISO 8601 wake time; must be in the future
Format date-time.
200objectHow many conversations changed
countintegerRequiredcurl -X POST "https://orbitwrite.com/api/v1/conversations/snooze-all" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "open",
"until": "2026-10-01T09:00:00Z"
}'{
"count": 0
}Requires approval:read. The reply-approval lane (Inbox 2.0): replies a gated member drafted that are waiting on an approval:decide holder. ?mine=true scopes to the caller's own held replies (their pending / rejected / failed) — the drafter's view; omit it for the whole workspace (the deciders' queue). ?status= narrows to one state.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
minestringScope to the caller's own held replies
"true""false"statusstring"pending""approved""rejected""sent""failed"200objectThe held replies
pendingRepliesPendingReplySummary[]RequiredidstringRequiredconversationIdstringRequiredorganizationIdstringRequiredstatusstringRequired"pending""approved""rejected""sent""failed"bodystringRequiredmediaPendingReplyMedia[]RequiredreplyToMessageIdstring | nullRequireddecisionNotestring | nullRequiredsendErrorstring | nullRequiredWhy the platform send failed, for a failed reply
createdAtstringRequiredISO 8601
decidedAtstring | nullRequiredISO 8601
decidablebooleanRequiredWhether an approval:decide holder can act on it now: pending, failed, or approved with a send that never finished (retryable after 10 minutes)
draftedByPendingReplyUserRequireddecidedByPendingReplyUser | any | nullRequiredconversationobjectRequiredplatformstringRequiredparticipantHandlestringRequiredparticipantDisplayNamestringRequiredcurl -X GET "https://orbitwrite.com/api/v1/conversations/pending-replies" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"pendingReplies": [
{
"id": "string",
"conversationId": "string",
"organizationId": "string",
"status": "pending",
"body": "string",
"media": [
{
"url": "string",
"mimeType": "string",
"altText": "string",
"sourceUrl": "string",
"videoUrl": "string",
"width": 0,
"height": 0,
"bytes": 0
}
],
"replyToMessageId": "string",
"decisionNote": "string",
"sendError": "string",
"createdAt": "string",
"decidedAt": "string",
"decidable": true,
"draftedBy": {
"id": "string",
"name": "string",
"avatarUrl": "string"
},
"decidedBy": {
"id": "string",
"name": "string",
"avatarUrl": "string"
},
"conversation": {
"platform": "string",
"participantHandle": "string",
"participantDisplayName": "string"
}
}
]
}Requires approval:decide. approve sends the drafted reply as-is; edit_approve sends body instead; reject records note back to the drafter and sends nothing. Approving publishes through the platform — a send that fails after approval returns 502 with the reply in failed, retryable by deciding again (as is an approval whose send never finished, once decidable turns true). A decider can't approve a reply attributed to themselves unless they could have sent it directly (inbox:reply). Approvals are rate limited per credential; rejections are not.
An API key or OAuth access token as a bearer token, or a signed-in session.
pendingReplyIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
actionstringRequired"approve""edit_approve""reject"bodystringReplacement text for edit_approve
notestringA note back to the drafter (the reject reason)
200objectThe decided reply
pendingReplyPendingReplySummaryRequiredidstringRequiredconversationIdstringRequiredorganizationIdstringRequiredstatusstringRequired"pending""approved""rejected""sent""failed"bodystringRequiredmediaPendingReplyMedia[]RequiredreplyToMessageIdstring | nullRequireddecisionNotestring | nullRequiredsendErrorstring | nullRequiredWhy the platform send failed, for a failed reply
createdAtstringRequiredISO 8601
decidedAtstring | nullRequiredISO 8601
decidablebooleanRequiredWhether an approval:decide holder can act on it now: pending, failed, or approved with a send that never finished (retryable after 10 minutes)
draftedByPendingReplyUserRequireddecidedByPendingReplyUser | any | nullRequiredconversationobjectRequiredplatformstringRequiredparticipantHandlestringRequiredparticipantDisplayNamestringRequired409objectAlready decided by someone else
errorstringRequired502objectApproved, but the platform rejected the send (retryable)
errorstringRequiredpendingReplyPendingReplySummaryRequiredidstringRequiredconversationIdstringRequiredorganizationIdstringRequiredstatusstringRequired"pending""approved""rejected""sent""failed"bodystringRequiredmediaPendingReplyMedia[]RequiredreplyToMessageIdstring | nullRequireddecisionNotestring | nullRequiredsendErrorstring | nullRequiredWhy the platform send failed, for a failed reply
createdAtstringRequiredISO 8601
decidedAtstring | nullRequiredISO 8601
decidablebooleanRequiredWhether an approval:decide holder can act on it now: pending, failed, or approved with a send that never finished (retryable after 10 minutes)
draftedByPendingReplyUserRequireddecidedByPendingReplyUser | any | nullRequiredconversationobjectRequiredplatformstringRequiredparticipantHandlestringRequiredparticipantDisplayNamestringRequiredcurl -X POST "https://orbitwrite.com/api/v1/conversations/pending-replies/{pendingReplyId}/decide" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "approve"
}'{
"pendingReply": {
"id": "string",
"conversationId": "string",
"organizationId": "string",
"status": "pending",
"body": "string",
"media": [
{
"url": "string",
"mimeType": "string",
"altText": "string",
"sourceUrl": "string",
"videoUrl": "string",
"width": 0,
"height": 0,
"bytes": 0
}
],
"replyToMessageId": "string",
"decisionNote": "string",
"sendError": "string",
"createdAt": "string",
"decidedAt": "string",
"decidable": true,
"draftedBy": {
"id": "string",
"name": "string",
"avatarUrl": "string"
},
"decidedBy": {
"id": "string",
"name": "string",
"avatarUrl": "string"
},
"conversation": {
"platform": "string",
"participantHandle": "string",
"participantDisplayName": "string"
}
}
}Requires a signed-in user and inbox:update. Assigns the oldest unassigned open conversation (optionally within one team inbox) to the caller, ignoring capacity and away by design. Returns { pulled: null } when nothing is waiting. A workspace API key has no one to assign to — use POST /conversations/{conversationId}/assign.
A signed-in session only. The Orbitwrite app calls this; an API key is refused.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
inboxIdstringAt least 1 character.
200objectThe claimed conversation, or null when none waiting
pulledConversationDetail | any | nullRequiredidstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
originalPostobject | nullRequiredThe published post being replied to, pinned on top
idstringRequiredThe post row this conversation hangs off (the inbox's postId), or the platform's own id for a native post.
groupIdstring | nullRequiredThe post's composer group — what GET /posts/draft, score and schedule take. Null for a post not published through Orbitwrite.
textstringRequiredplatformUrlstring | nullRequiredplatformPostIdstring | nullRequiredcreatedAtstring | nullRequiredmessagesConversationMessage[]RequiredidstringRequireddirectionstringRequired"in""out"textstringRequiredmediaConversationMedia[]RequiredImages / GIFs / video attached to the message
permalinkUrlstring | nullRequiredcreatedAtstring | nullRequiredISO 8601
platformReplyIdstring | nullRequiredRemote id of this reply — the target when replying to it
parentPlatformIdstring | nullRequiredRemote id of the post/reply this one replies to (thread structure)
contentExpiredbooleanRequiredThe text, media or author has passed the platform's retention window and been dropped; the detail read re-fetches them when it can.
textExpiredbooleanRequiredNarrower than contentExpired: the text and media themselves have passed the retention window (LinkedIn: 48 hours), rather than only the author's profile (24 hours). An empty text with this false means the platform never supplied any.
curl -X POST "https://orbitwrite.com/api/v1/conversations/pull" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "string",
"inboxId": "string"
}'{
"pulled": {
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}
}Requires inbox:read. The conversation, the original published post pinned on top, and every message (incoming replies and our outbound answers) in chronological order.
An API key or OAuth access token as a bearer token, or a signed-in session.
conversationIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200ConversationDetailThe conversation detail
idstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
originalPostobject | nullRequiredThe published post being replied to, pinned on top
idstringRequiredThe post row this conversation hangs off (the inbox's postId), or the platform's own id for a native post.
groupIdstring | nullRequiredThe post's composer group — what GET /posts/draft, score and schedule take. Null for a post not published through Orbitwrite.
textstringRequiredplatformUrlstring | nullRequiredplatformPostIdstring | nullRequiredcreatedAtstring | nullRequiredmessagesConversationMessage[]RequiredidstringRequireddirectionstringRequired"in""out"textstringRequiredmediaConversationMedia[]RequiredImages / GIFs / video attached to the message
permalinkUrlstring | nullRequiredcreatedAtstring | nullRequiredISO 8601
platformReplyIdstring | nullRequiredRemote id of this reply — the target when replying to it
parentPlatformIdstring | nullRequiredRemote id of the post/reply this one replies to (thread structure)
contentExpiredbooleanRequiredThe text, media or author has passed the platform's retention window and been dropped; the detail read re-fetches them when it can.
textExpiredbooleanRequiredNarrower than contentExpired: the text and media themselves have passed the retention window (LinkedIn: 48 hours), rather than only the author's profile (24 hours). An empty text with this false means the platform never supplied any.
curl -X GET "https://orbitwrite.com/api/v1/conversations/{conversationId}" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}Requires inbox:update. Flips the conversation's shared status. Resolving moves it out of the open list into the resolved category for the whole workspace.
An API key or OAuth access token as a bearer token, or a signed-in session.
conversationIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
statusstringRequired"open""resolved"200ConversationDetailThe updated conversation
idstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
originalPostobject | nullRequiredThe published post being replied to, pinned on top
idstringRequiredThe post row this conversation hangs off (the inbox's postId), or the platform's own id for a native post.
groupIdstring | nullRequiredThe post's composer group — what GET /posts/draft, score and schedule take. Null for a post not published through Orbitwrite.
textstringRequiredplatformUrlstring | nullRequiredplatformPostIdstring | nullRequiredcreatedAtstring | nullRequiredmessagesConversationMessage[]RequiredidstringRequireddirectionstringRequired"in""out"textstringRequiredmediaConversationMedia[]RequiredImages / GIFs / video attached to the message
permalinkUrlstring | nullRequiredcreatedAtstring | nullRequiredISO 8601
platformReplyIdstring | nullRequiredRemote id of this reply — the target when replying to it
parentPlatformIdstring | nullRequiredRemote id of the post/reply this one replies to (thread structure)
contentExpiredbooleanRequiredThe text, media or author has passed the platform's retention window and been dropped; the detail read re-fetches them when it can.
textExpiredbooleanRequiredNarrower than contentExpired: the text and media themselves have passed the retention window (LinkedIn: 48 hours), rather than only the author's profile (24 hours). An empty text with this false means the platform never supplied any.
curl -X PATCH "https://orbitwrite.com/api/v1/conversations/{conversationId}" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "open"
}'{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}Requires inbox:update. Sets the teammate who owns this conversation, or pass assigneeUserId: null to return it to the shared/unassigned pool. Ownership is shared workspace state (every role can assign), and the change is audited. A non-null assignee must be a member of the workspace.
An API key or OAuth access token as a bearer token, or a signed-in session.
conversationIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
assigneeUserIdstring | nullRequiredThe teammate to own this conversation, or null to unassign
At least 1 character.
200ConversationDetailThe updated conversation
idstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
originalPostobject | nullRequiredThe published post being replied to, pinned on top
idstringRequiredThe post row this conversation hangs off (the inbox's postId), or the platform's own id for a native post.
groupIdstring | nullRequiredThe post's composer group — what GET /posts/draft, score and schedule take. Null for a post not published through Orbitwrite.
textstringRequiredplatformUrlstring | nullRequiredplatformPostIdstring | nullRequiredcreatedAtstring | nullRequiredmessagesConversationMessage[]RequiredidstringRequireddirectionstringRequired"in""out"textstringRequiredmediaConversationMedia[]RequiredImages / GIFs / video attached to the message
permalinkUrlstring | nullRequiredcreatedAtstring | nullRequiredISO 8601
platformReplyIdstring | nullRequiredRemote id of this reply — the target when replying to it
parentPlatformIdstring | nullRequiredRemote id of the post/reply this one replies to (thread structure)
contentExpiredbooleanRequiredThe text, media or author has passed the platform's retention window and been dropped; the detail read re-fetches them when it can.
textExpiredbooleanRequiredNarrower than contentExpired: the text and media themselves have passed the retention window (LinkedIn: 48 hours), rather than only the author's profile (24 hours). An empty text with this false means the platform never supplied any.
curl -X POST "https://orbitwrite.com/api/v1/conversations/{conversationId}/assign" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"assigneeUserId": "string"
}'{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}Requires inbox:update. Advances the shared read watermark to now, clearing the unread indicator for the whole workspace.
An API key or OAuth access token as a bearer token, or a signed-in session.
conversationIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200ConversationDetailThe updated conversation
idstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
originalPostobject | nullRequiredThe published post being replied to, pinned on top
idstringRequiredThe post row this conversation hangs off (the inbox's postId), or the platform's own id for a native post.
groupIdstring | nullRequiredThe post's composer group — what GET /posts/draft, score and schedule take. Null for a post not published through Orbitwrite.
textstringRequiredplatformUrlstring | nullRequiredplatformPostIdstring | nullRequiredcreatedAtstring | nullRequiredmessagesConversationMessage[]RequiredidstringRequireddirectionstringRequired"in""out"textstringRequiredmediaConversationMedia[]RequiredImages / GIFs / video attached to the message
permalinkUrlstring | nullRequiredcreatedAtstring | nullRequiredISO 8601
platformReplyIdstring | nullRequiredRemote id of this reply — the target when replying to it
parentPlatformIdstring | nullRequiredRemote id of the post/reply this one replies to (thread structure)
contentExpiredbooleanRequiredThe text, media or author has passed the platform's retention window and been dropped; the detail read re-fetches them when it can.
textExpiredbooleanRequiredNarrower than contentExpired: the text and media themselves have passed the retention window (LinkedIn: 48 hours), rather than only the author's profile (24 hours). An empty text with this false means the platform never supplied any.
curl -X POST "https://orbitwrite.com/api/v1/conversations/{conversationId}/read" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "string"
}'{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}Requires inbox:update. Clears the shared read watermark so the conversation shows as unread again (when its latest activity is an incoming reply). The inverse of /read.
An API key or OAuth access token as a bearer token, or a signed-in session.
conversationIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200ConversationDetailThe updated conversation
idstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
originalPostobject | nullRequiredThe published post being replied to, pinned on top
idstringRequiredThe post row this conversation hangs off (the inbox's postId), or the platform's own id for a native post.
groupIdstring | nullRequiredThe post's composer group — what GET /posts/draft, score and schedule take. Null for a post not published through Orbitwrite.
textstringRequiredplatformUrlstring | nullRequiredplatformPostIdstring | nullRequiredcreatedAtstring | nullRequiredmessagesConversationMessage[]RequiredidstringRequireddirectionstringRequired"in""out"textstringRequiredmediaConversationMedia[]RequiredImages / GIFs / video attached to the message
permalinkUrlstring | nullRequiredcreatedAtstring | nullRequiredISO 8601
platformReplyIdstring | nullRequiredRemote id of this reply — the target when replying to it
parentPlatformIdstring | nullRequiredRemote id of the post/reply this one replies to (thread structure)
contentExpiredbooleanRequiredThe text, media or author has passed the platform's retention window and been dropped; the detail read re-fetches them when it can.
textExpiredbooleanRequiredNarrower than contentExpired: the text and media themselves have passed the retention window (LinkedIn: 48 hours), rather than only the author's profile (24 hours). An empty text with this false means the platform never supplied any.
curl -X POST "https://orbitwrite.com/api/v1/conversations/{conversationId}/unread" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "string"
}'{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}Requires inbox:update. Hides the conversation from the open list until until, moving it to the shared "snoozed" category. A background sweep returns it to the open list (as unread) when that time passes.
An API key or OAuth access token as a bearer token, or a signed-in session.
conversationIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
untilstringRequiredISO 8601 wake time; must be in the future
Format date-time.
200ConversationDetailThe snoozed conversation
idstringRequiredplatformstringRequiredPlatform id, e.g. "bluesky"
accountIdstringRequiredtypestringRequired"reply""mention"statusstringRequired"open""resolved""snoozed"participantConversationParticipantRequiredhandlestringRequireddisplayNamestringRequiredavatarUrlstring | nullRequiredlastMessagePreviewstringRequiredlastMediaKindstring | nullRequiredMedia kind of the latest reply for a list glyph; null if text-only
"image""gif""video"nulllastMessageAtstring | nullRequiredISO 8601
unreadbooleanRequiredsnoozedUntilstring | nullRequiredISO 8601 wake time when status is "snoozed"; null otherwise
assigneeUserIdstring | nullRequiredUser id of the teammate who owns this conversation; null when unassigned
contentExpiredbooleanRequiredThe participant or preview has passed the platform's retention window (LinkedIn caches member profiles for 24 hours and keeps member posts and comments for 48) and been dropped. The list and detail reads re-fetch them when they can.
originalPostobject | nullRequiredThe published post being replied to, pinned on top
idstringRequiredThe post row this conversation hangs off (the inbox's postId), or the platform's own id for a native post.
groupIdstring | nullRequiredThe post's composer group — what GET /posts/draft, score and schedule take. Null for a post not published through Orbitwrite.
textstringRequiredplatformUrlstring | nullRequiredplatformPostIdstring | nullRequiredcreatedAtstring | nullRequiredmessagesConversationMessage[]RequiredidstringRequireddirectionstringRequired"in""out"textstringRequiredmediaConversationMedia[]RequiredImages / GIFs / video attached to the message
permalinkUrlstring | nullRequiredcreatedAtstring | nullRequiredISO 8601
platformReplyIdstring | nullRequiredRemote id of this reply — the target when replying to it
parentPlatformIdstring | nullRequiredRemote id of the post/reply this one replies to (thread structure)
contentExpiredbooleanRequiredThe text, media or author has passed the platform's retention window and been dropped; the detail read re-fetches them when it can.
textExpiredbooleanRequiredNarrower than contentExpired: the text and media themselves have passed the retention window (LinkedIn: 48 hours), rather than only the author's profile (24 hours). An empty text with this false means the platform never supplied any.
curl -X POST "https://orbitwrite.com/api/v1/conversations/{conversationId}/snooze" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"until": "2026-10-01T09:00:00Z"
}'{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}Publishes a reply through the platform (reusing the adapter's comment path — no scheduled post is created) and appends it to the thread, when the caller holds inbox:reply. A caller who lacks it but holds approval:request (a gated member, or a key scoped that way) does not 403: the reply is captured as a blocking approval and returned with 202 — it never reaches the platform until an approval:decide holder releases it (see /conversations/pending-replies). The reply targets the conversation's post, or the specific comment named by replyToMessageId.
An API key or OAuth access token as a bearer token, or a signed-in session.
200ConversationDetailThe conversation with the sent reply appended
202objectThe caller can't publish directly; the reply is held for approval
400ErrorInvalid request
401ErrorNot signed in
403ErrorNot a member of the organization
404ErrorThe resource does not exist
429ErrorRate limit exceeded
502objectThe platform rejected the reply
curl -X POST "https://orbitwrite.com/api/v1/conversations/{conversationId}/messages" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "",
"replyToMessageId": "string",
"media": [
{
"url": "string",
"mimeType": "string"
}
],
"organizationId": "string"
}'{
"id": "string",
"platform": "string",
"accountId": "string",
"type": "reply",
"status": "open",
"participant": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"lastMessagePreview": "string",
"lastMediaKind": "image",
"lastMessageAt": "string",
"unread": true,
"snoozedUntil": "string",
"assigneeUserId": "string",
"contentExpired": true,
"originalPost": {
"id": "string",
"groupId": "string",
"text": "string",
"platformUrl": "string",
"platformPostId": "string",
"author": {
"handle": "string",
"avatarUrl": "string"
},
"createdAt": "string"
},
"messages": [
{
"id": "string",
"direction": "in",
"author": {
"handle": "string",
"displayName": "string",
"avatarUrl": "string"
},
"text": "string",
"media": [
{
"kind": "image",
"url": "string",
"videoUrl": "string",
"altText": "string",
"width": 0,
"height": 0
}
],
"permalinkUrl": "string",
"createdAt": "string",
"platformReplyId": "string",
"parentPlatformId": "string",
"contentExpired": true,
"textExpired": true
}
]
}