API reference

Analytics

16 endpoints, served at https://orbitwrite.com/api/v1. Each one lists the credentials it accepts, its parameters and every response it can give.

How the workspace's posts did over a window#

GET/api/v1/analytics/results

The window's published posts (newest first, at most limit, each thread counted once) with their latest numbers, the totals, the post with the most engagements, and each channel's follower change from the daily snapshots. Numbers come from the analytics history the worker keeps; a post it hasn't read yet is read from the platform. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

daysinteger

How far back to look, ending now (or at to). Defaults to 7.

Between 1 and 90.

fromstring

Start of the window, an ISO instant. Overrides days.

tostring

End of the window, an ISO instant. Defaults to now.

limitinteger

Most posts to read. Defaults to 15.

Between 1 and 25.

accountIdsstring | string[]

Repeat to narrow to these channels.

Responses

200WorkspaceResults

The window's results

Response attributes
fromstringRequired
tostringRequired
postsobject[]Required
Child attributes
postIdstringRequired

The post row — what GET /posts/{postId}/analytics and the inbox's postId filter take.

groupIdstring | any | nullRequired

The composer group — what every post-addressed route (read, score, schedule) takes.

accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
textstringRequired
publishedAtstringRequired
platformUrlstring | any | nullRequired
statusstringRequired

unsupported: the network reports no post metrics. no_data_yet: no platform id was captured. unavailable: the platform didn't answer (auth, rate limit, outage).

  • "ok"
  • "unsupported"
  • "no_data_yet"
  • "unavailable"
metricsobject[]Required
Child attributes
keystringRequired
labelstringRequired
valuenumberRequired
formatstringRequired
  • "count"
  • "percent"
engagementsnumber | any | nullRequired

Reactions, comments, reposts, quotes and saves summed. Null when the post has no numbers.

truncatedbooleanRequired

The window held more posts than limit.

totalsobject[]Required
Child attributes
keystringRequired
labelstringRequired
valuenumberRequired
topPostIdstring | any | nullRequired

The row id of the post with the most engagements; null when no post has positive summed engagements (none reported numbers, or every total is zero).

topGroupIdstring | any | nullRequired

That same post's composer group — the id to read, score or reschedule it by.

historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

followersobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
startnumberRequired
endnumberRequired
changenumberRequired
fromstringRequired
tostringRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/results
curl -X GET "https://orbitwrite.com/api/v1/analytics/results" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "posts": [
    {
      "postId": "string",
      "groupId": "string",
      "accountId": "string",
      "platform": "string",
      "channel": "string",
      "text": "string",
      "publishedAt": "string",
      "platformUrl": "string",
      "status": "ok",
      "metrics": [
        {
          "key": "string",
          "label": "string",
          "value": 0,
          "format": "count"
        }
      ],
      "engagements": 0
    }
  ],
  "truncated": true,
  "totals": [
    {
      "key": "string",
      "label": "string",
      "value": 0
    }
  ],
  "topPostId": "string",
  "topGroupId": "string",
  "historyDays": 0,
  "followers": [
    {
      "accountId": "string",
      "platform": "string",
      "channel": "string",
      "start": 0,
      "end": 0,
      "change": 0,
      "from": "string",
      "to": "string"
    }
  ]
}

Totals for the window, against the window before#

GET/api/v1/analytics/overview

Each metric summed over the posts published in the window (each post's latest numbers, a thread counted once), with the same totals for a comparison window and the change. The comparison is compareFrom/compareTo when given, else the equal stretch just before the window. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

periodstring

A calendar period in timezone: this_month (the 1st to now) or last_month. Overrides days; from/to override it.

  • "this_month"
  • "last_month"
daysinteger | null

How far back to look, ending now (or at to). At most 366; the plan's analytics history may cut it shorter.

fromstring

Start of the window: an ISO instant, or a YYYY-MM-DD day in timezone. Overrides days.

tostring

End of the window: an ISO instant, or a YYYY-MM-DD day in timezone (included whole). Defaults to now.

accountIdsstring | string[]

Repeat to narrow to these channels.

timezonestring

IANA zone days and hours are counted in. Defaults to the workspace's, then UTC.

campaignIdstring

Only this campaign's posts.

tagIdsstring | string[]

Repeat to narrow to posts carrying any of these tags. With campaignId, posts must match both.

compareFromstring

Start of the window to compare against: an ISO instant or a YYYY-MM-DD day. Defaults to the same length of time just before the window.

comparestring

Without compareFrom/compareTo: previous (the default, the same length of time just before the window) or year (the same window a year earlier).

  • "previous"
  • "year"
compareTostring

End of the window to compare against: an ISO instant or a YYYY-MM-DD day (included whole). Required with compareFrom.

Responses

200AnalyticsOverview

The window's totals

Response attributes
fromstringRequired
tostringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

timezonestringRequired
previousobject | any | nullRequired

The window compared against: compareFrom/compareTo when given, else the same length of time just before the window. Null when the plan's history doesn't reach it.

Child attributes
fromstringRequired
tostringRequired
postsobjectRequired
Child attributes
currentintegerRequired
previousinteger | any | nullRequired
metricsobject[]Required
Child attributes
metricstringRequired
  • "engagements"
  • "impressions"
  • "reactions"
  • "comments"
  • "reposts"
  • "quotes"
  • "saves"
  • "clicks"
  • "profileClicks"
  • "videoViews"
labelstringRequired
valuenumber | any | nullRequired

Null when no post in the window reported it.

previousnumber | any | nullRequired
changePercentnumber | any | nullRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/overview
curl -X GET "https://orbitwrite.com/api/v1/analytics/overview" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "historyDays": 0,
  "timezone": "string",
  "previous": {
    "from": "string",
    "to": "string"
  },
  "posts": {
    "current": 0,
    "previous": 0
  },
  "metrics": [
    {
      "metric": "engagements",
      "label": "string",
      "value": 0,
      "previous": 0,
      "changePercent": 0
    }
  ]
}

A metric received per day or week#

GET/api/v1/analytics/series

How much of a metric the workspace's posts received on each day (or week, starting Monday) of the window, posts of any age: each post's daily change between reads, summed. Everything a post earned before its first read is dated on the day it went out. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

periodstring

A calendar period in timezone: this_month (the 1st to now) or last_month. Overrides days; from/to override it.

  • "this_month"
  • "last_month"
daysinteger | null

How far back to look, ending now (or at to). At most 366; the plan's analytics history may cut it shorter.

fromstring

Start of the window: an ISO instant, or a YYYY-MM-DD day in timezone. Overrides days.

tostring

End of the window: an ISO instant, or a YYYY-MM-DD day in timezone (included whole). Defaults to now.

accountIdsstring | string[]

Repeat to narrow to these channels.

timezonestring

IANA zone days and hours are counted in. Defaults to the workspace's, then UTC.

campaignIdstring

Only this campaign's posts.

tagIdsstring | string[]

Repeat to narrow to posts carrying any of these tags. With campaignId, posts must match both.

metricstring

Defaults to engagements.

  • "engagements"
  • "impressions"
  • "reactions"
  • "comments"
  • "reposts"
  • "quotes"
  • "saves"
  • "clicks"
  • "profileClicks"
  • "videoViews"
intervalstring

Defaults to day.

  • "day"
  • "week"

Responses

200AnalyticsSeries

One point per day or week

Response attributes
fromstringRequired
tostringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

timezonestringRequired
metricstringRequired
  • "engagements"
  • "impressions"
  • "reactions"
  • "comments"
  • "reposts"
  • "quotes"
  • "saves"
  • "clicks"
  • "profileClicks"
  • "videoViews"
intervalstringRequired
  • "day"
  • "week"
pointsobject[]Required
Child attributes
datestringRequired

YYYY-MM-DD in timezone; a week's Monday.

valuenumberRequired
totalnumberRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/series
curl -X GET "https://orbitwrite.com/api/v1/analytics/series" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "historyDays": 0,
  "timezone": "string",
  "metric": "engagements",
  "interval": "day",
  "points": [
    {
      "date": "string",
      "value": 0
    }
  ],
  "total": 0
}

Followers per channel over time#

GET/api/v1/analytics/followers

Each channel's follower count per day in the window (the day's last reading; channels are read four times a day, X once), with the first, the last and the change. A channel with no reading in the window is absent. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

periodstring

A calendar period in timezone: this_month (the 1st to now) or last_month. Overrides days; from/to override it.

  • "this_month"
  • "last_month"
daysinteger | null

How far back to look, ending now (or at to). At most 366; the plan's analytics history may cut it shorter.

fromstring

Start of the window: an ISO instant, or a YYYY-MM-DD day in timezone. Overrides days.

tostring

End of the window: an ISO instant, or a YYYY-MM-DD day in timezone (included whole). Defaults to now.

accountIdsstring | string[]

Repeat to narrow to these channels.

timezonestring

IANA zone days and hours are counted in. Defaults to the workspace's, then UTC.

Responses

200AnalyticsFollowers

Follower series per channel

Response attributes
fromstringRequired
tostringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

timezonestringRequired
channelsobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
pointsobject[]Required
Child attributes
datestringRequired
followersnumberRequired
startnumberRequired
endnumberRequired
changenumberRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/followers
curl -X GET "https://orbitwrite.com/api/v1/analytics/followers" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "historyDays": 0,
  "timezone": "string",
  "channels": [
    {
      "accountId": "string",
      "platform": "string",
      "channel": "string",
      "points": [
        {
          "date": "string",
          "followers": 0
        }
      ],
      "start": 0,
      "end": 0,
      "change": 0
    }
  ]
}

When the channels' posts do best#

GET/api/v1/analytics/best-times

An hour-of-week heatmap of engagement (the same rows the scheduling heatmap uses), from the posts published in the window, 90 days by default. Below threshold posts it is the researched heuristic for the channels' platform, and source says so. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

periodstring

A calendar period in timezone: this_month (the 1st to now) or last_month. Overrides days; from/to override it.

  • "this_month"
  • "last_month"
daysinteger | null

How far back to look, ending now (or at to). At most 366; the plan's analytics history may cut it shorter.

fromstring

Start of the window: an ISO instant, or a YYYY-MM-DD day in timezone. Overrides days.

tostring

End of the window: an ISO instant, or a YYYY-MM-DD day in timezone (included whole). Defaults to now.

accountIdsstring | string[]

Repeat to narrow to these channels.

timezonestring

IANA zone days and hours are counted in. Defaults to the workspace's, then UTC.

campaignIdstring

Only this campaign's posts.

tagIdsstring | string[]

Repeat to narrow to posts carrying any of these tags. With campaignId, posts must match both.

Responses

200AnalyticsBestTimes

The heatmap

Response attributes
fromstringRequired
tostringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

timezonestringRequired
sourcestringRequired

measured: from these channels' own posts. heuristic: a researched per-platform profile, used until there are threshold posts. Never describe a heuristic matrix as data about this audience.

  • "measured"
  • "heuristic"
sampleSizeintegerRequired
thresholdintegerRequired
daysstring[]Required
rowsobject[]Required
Child attributes
minutesintegerRequired
labelstringRequired
matrixnumber[][]Required

[day][row], Monday first, 0..1 with the strongest cell 1.

countsinteger[][] | any | nullRequired

Posts behind each cell (measured only). A 0 cell with 0 posts is empty, not bad.

platformstring | any | nullRequired
researchedboolean | any | nullRequired
reasonstring | any | nullRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/best-times
curl -X GET "https://orbitwrite.com/api/v1/analytics/best-times" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "historyDays": 0,
  "timezone": "string",
  "source": "measured",
  "sampleSize": 0,
  "threshold": 0,
  "days": [
    "string"
  ],
  "rows": [
    {
      "minutes": 0,
      "label": "string"
    }
  ],
  "matrix": [
    [
      0
    ]
  ],
  "counts": [
    [
      0
    ]
  ],
  "platform": "string",
  "researched": true,
  "reason": "string"
}

Posts ranked by a metric#

GET/api/v1/analytics/posts

Posts published in the window, best first by metric (each post's channels summed, from their latest numbers), cursor-paged. A post where no channel reported the metric is left out rather than ranked as zero. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

periodstring

A calendar period in timezone: this_month (the 1st to now) or last_month. Overrides days; from/to override it.

  • "this_month"
  • "last_month"
daysinteger | null

How far back to look, ending now (or at to). At most 366; the plan's analytics history may cut it shorter.

fromstring

Start of the window: an ISO instant, or a YYYY-MM-DD day in timezone. Overrides days.

tostring

End of the window: an ISO instant, or a YYYY-MM-DD day in timezone (included whole). Defaults to now.

accountIdsstring | string[]

Repeat to narrow to these channels.

timezonestring

IANA zone days and hours are counted in. Defaults to the workspace's, then UTC.

campaignIdstring

Only this campaign's posts.

tagIdsstring | string[]

Repeat to narrow to posts carrying any of these tags. With campaignId, posts must match both.

metricstring

Defaults to engagements.

  • "engagements"
  • "impressions"
  • "reactions"
  • "comments"
  • "reposts"
  • "quotes"
  • "saves"
  • "clicks"
  • "profileClicks"
  • "videoViews"
limitinteger | null

Posts per page, at most 50. Defaults to 20.

cursorstring

nextCursor from the previous page.

Responses

200AnalyticsTopPosts

A page of ranked posts

Response attributes
fromstringRequired
tostringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

metricstringRequired
  • "engagements"
  • "impressions"
  • "reactions"
  • "comments"
  • "reposts"
  • "quotes"
  • "saves"
  • "clicks"
  • "profileClicks"
  • "videoViews"
entriesobject[]Required
Child attributes
groupIdstring | any | nullRequired
postIdsstring[]Required
textstringRequired
publishedAtstringRequired
channelsobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
platformUrlstring | any | nullRequired
mediaobject | any | nullRequired

The post's first attachment, for a thumbnail, and how many it has. Null without media.

Child attributes
urlstringRequired
mimeTypestringRequired
countintegerRequired
tagsobject[]Required
Child attributes
idstringRequired
namestringRequired
colorstringRequired
valuenumberRequired

The ranked metric, summed over the post's channels.

metricsAnalyticsMetricValuesRequired

Each metric summed; null where nothing reported it (Bluesky has no impressions, say), never 0 for 'unknown'.

Child attributes
engagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
reactionsnumber | any | nullRequired
commentsnumber | any | nullRequired
repostsnumber | any | nullRequired
quotesnumber | any | nullRequired
savesnumber | any | nullRequired
clicksnumber | any | nullRequired
profileClicksnumber | any | nullRequired
videoViewsnumber | any | nullRequired
nextCursorstring | any | nullRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/posts
curl -X GET "https://orbitwrite.com/api/v1/analytics/posts" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "historyDays": 0,
  "metric": "engagements",
  "entries": [
    {
      "groupId": "string",
      "postIds": [
        "string"
      ],
      "text": "string",
      "publishedAt": "string",
      "channels": [
        {
          "accountId": "string",
          "platform": "string",
          "channel": "string",
          "platformUrl": "string"
        }
      ],
      "media": {
        "url": "string",
        "mimeType": "string",
        "count": 0
      },
      "tags": [
        {
          "id": "string",
          "name": "string",
          "color": "string"
        }
      ],
      "value": 0,
      "metrics": {
        "engagements": 0,
        "impressions": 0,
        "reactions": 0,
        "comments": 0,
        "reposts": 0,
        "quotes": 0,
        "saves": 0,
        "clicks": 0,
        "profileClicks": 0,
        "videoViews": 0
      }
    }
  ],
  "nextCursor": "string"
}

Which platform, or which kind of post, does best#

GET/api/v1/analytics/breakdown

Posts published in the window split by platform or by format (video, thread, image carousel, single image, document, poll, quote, link, text only), each group with its post count, total and average engagements per post, impressions and engagement rate. Ranked by the average, so a busy channel or format doesn't win on volume. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

periodstring

A calendar period in timezone: this_month (the 1st to now) or last_month. Overrides days; from/to override it.

  • "this_month"
  • "last_month"
daysinteger | null

How far back to look, ending now (or at to). At most 366; the plan's analytics history may cut it shorter.

fromstring

Start of the window: an ISO instant, or a YYYY-MM-DD day in timezone. Overrides days.

tostring

End of the window: an ISO instant, or a YYYY-MM-DD day in timezone (included whole). Defaults to now.

accountIdsstring | string[]

Repeat to narrow to these channels.

timezonestring

IANA zone days and hours are counted in. Defaults to the workspace's, then UTC.

campaignIdstring

Only this campaign's posts.

tagIdsstring | string[]

Repeat to narrow to posts carrying any of these tags. With campaignId, posts must match both.

bystringRequired
  • "platform"
  • "format"
  • "topic"

Responses

200AnalyticsBreakdown

The groups, best first

Response attributes
fromstringRequired
tostringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

timezonestringRequired
bystringRequired
  • "platform"
  • "format"
  • "topic"
minPostsintegerRequired

Groups with fewer posts than this are flagged few.

rowsobject[]Required
Child attributes
keystringRequired

A platform id (twitter, linkedin_page, …) or a format (video, thread, …).

labelstringRequired
postsintegerRequired
engagementsnumber | any | nullRequired
avgEngagementsnumber | any | nullRequired

Engagements per post that reported any. What the rows are ranked by.

impressionsnumber | any | nullRequired
engagementRatenumber | any | nullRequired

Engagements ÷ impressions over the posts that report impressions, as a percentage. Null where none do (Bluesky).

fewbooleanRequired

Too few posts to judge; show it greyed rather than as a finding.

400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/breakdown
curl -X GET "https://orbitwrite.com/api/v1/analytics/breakdown?by=by" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "historyDays": 0,
  "timezone": "string",
  "by": "platform",
  "minPosts": 0,
  "rows": [
    {
      "key": "string",
      "label": "string",
      "posts": 0,
      "engagements": 0,
      "avgEngagements": 0,
      "impressions": 0,
      "engagementRate": 0,
      "few": true
    }
  ]
}

What's working: the strongest patterns in the window's posts#

GET/api/v1/analytics/insights

Patterns in the posts published in the window (default the last 30 days), strongest first: which format, content type (announcements, tutorials, events, case studies, …), tone, opening-line length, post length, weekday, time of day, links, hashtags, questions and tags did better or worse than the rest. Each post is judged against its own channel's median, so a pattern isn't just the bigger channel winning. Also timing: days and times that are working, not working, or untried. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

periodstring

A calendar period in timezone: this_month (the 1st to now) or last_month. Overrides days; from/to override it.

  • "this_month"
  • "last_month"
daysinteger | null

How far back to look, ending now (or at to). At most 366; the plan's analytics history may cut it shorter.

fromstring

Start of the window: an ISO instant, or a YYYY-MM-DD day in timezone. Overrides days.

tostring

End of the window: an ISO instant, or a YYYY-MM-DD day in timezone (included whole). Defaults to now.

accountIdsstring | string[]

Repeat to narrow to these channels.

timezonestring

IANA zone days and hours are counted in. Defaults to the workspace's, then UTC.

campaignIdstring

Only this campaign's posts.

tagIdsstring | string[]

Repeat to narrow to posts carrying any of these tags. With campaignId, posts must match both.

limitinteger | null

How many insights, 1–10. Defaults to 3.

Responses

200AnalyticsInsights

The insights, strongest first

Response attributes
fromstringRequired
tostringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history cut the window short: how many days back it reaches.

timezonestringRequired
postsAnalysedintegerRequired

Posts published in the window with numbers to judge, each against its own channel.

minPostsintegerRequired

Either side of a comparison needs at least this many posts.

insightsobject[]Required
Child attributes
idstringRequired
dimensionstringRequired

What the posts were grouped by: format, topic (what the post is about: announcement, tutorial, event, case study, …), sentiment (its tone), hook (opening-line length), length, day, time (of day), link, hashtags, question, or tag.

  • "format"
  • "topic"
  • "sentiment"
  • "hook"
  • "length"
  • "day"
  • "time"
  • "link"
  • "hashtags"
  • "question"
  • "tag"
groupstringRequired

The group within the dimension: a format, a bucket, a weekday (0 = Sunday), a tag id, or yes.

directionstringRequired
  • "better"
  • "worse"
metricstringRequired
  • "impressions"
  • "engagements"
titlestringRequired
summarystringRequired

One sentence with the numbers.

liftnumberRequired

The group's average against the rest's, each post measured against its channel's median: 2 is twice as well, 0.5 half.

postsintegerRequired
comparedPostsintegerRequired
confidencestringRequired
  • "low"
  • "medium"
  • "high"
exampleGroupIdsstring[]Required

Up to three of the group's posts that show it best.

timingobjectRequired

Which days and times work, which don't, and which haven't been tried, each post judged against its channel.

Child attributes
daysobject[]Required

Each weekday, Sunday first: its posts, and their engagement against the rest (null with too few posts to judge).

Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
liftnumber | any | nullRequired
timesobject[]Required

Each time of day (night, morning, afternoon, evening), the same way.

Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
liftnumber | any | nullRequired
bestobjectRequired
Child attributes
dayobject | any | nullRequired
Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
liftnumber | any | nullRequired
timeobject | any | nullRequired
Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
liftnumber | any | nullRequired
experimentsobject[]Required

Up to three things to try: days or times doing worse than the rest, then ones nothing has gone out on.

Child attributes
kindstringRequired
  • "weak"
  • "untested"
dimensionstringRequired
  • "day"
  • "time"
keystringRequired
titlestringRequired
summarystringRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/insights
curl -X GET "https://orbitwrite.com/api/v1/analytics/insights" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "from": "string",
  "to": "string",
  "historyDays": 0,
  "timezone": "string",
  "postsAnalysed": 0,
  "minPosts": 0,
  "insights": [
    {
      "id": "string",
      "dimension": "format",
      "group": "string",
      "direction": "better",
      "metric": "impressions",
      "title": "string",
      "summary": "string",
      "lift": 0,
      "posts": 0,
      "comparedPosts": 0,
      "confidence": "low",
      "exampleGroupIds": [
        "string"
      ]
    }
  ],
  "timing": {
    "days": [
      {
        "key": "string",
        "label": "string",
        "posts": 0,
        "lift": 0
      }
    ],
    "times": [
      {
        "key": "string",
        "label": "string",
        "posts": 0,
        "lift": 0
      }
    ],
    "best": {
      "day": {
        "key": "string",
        "label": "string",
        "posts": 0,
        "lift": 0
      },
      "time": {
        "key": "string",
        "label": "string",
        "posts": 0,
        "lift": 0
      }
    },
    "experiments": [
      {
        "kind": "weak",
        "dimension": "day",
        "key": "string",
        "title": "string",
        "summary": "string"
      }
    ]
  }
}

When each channel's numbers were and will be read#

GET/api/v1/analytics/sync-status

Per channel: whether its posts are collected, the last and next post read, the last follower reading, and any pause. Tells a quiet chart from a stalled one. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200AnalyticsSyncStatus

Collection status per channel

Response attributes
channelsobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
collectedbooleanRequired

Whether the worker re-reads this channel's posts at all.

lastPolledAtstring | any | nullRequired
nextPollAtstring | any | nullRequired
followersAtstring | any | nullRequired
pausedobject | any | nullRequired

Set while the platform is refusing reads (rate_limited, auth, or X's daily_cap).

Child attributes
untilstringRequired
reasonstringRequired
xobject | any | nullRequired

Only with an X channel: whether X is being read this month. X reads are billed, and capped.

Child attributes
enabledbooleanRequired
budgetstringRequired
  • "open"
  • "spent"
  • "unknown"
resetsAtstring | any | nullRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/sync-status
curl -X GET "https://orbitwrite.com/api/v1/analytics/sync-status" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "channels": [
    {
      "accountId": "string",
      "platform": "string",
      "channel": "string",
      "collected": true,
      "lastPolledAt": "string",
      "nextPollAt": "string",
      "followersAt": "string",
      "paused": {
        "until": "string",
        "reason": "string"
      }
    }
  ],
  "x": {
    "enabled": true,
    "budget": "open",
    "resetsAt": "string"
  }
}

One post's numbers over time#

GET/api/v1/posts/{groupId}/metrics

Every read of the post's numbers, per channel, oldest first: what a chart of how it grew is drawn from. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Path parameters

groupIdstringRequired

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200PostMetricSeries

The post's reads

Response attributes
groupIdstringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history hid this post's earlier reads.

channelsobject[]Required
Child attributes
accountIdstringRequired
postIdstringRequired
platformstringRequired
channelstring | any | nullRequired
pointsobject[]Required
Child attributes
fetchedAtstringRequired
metricsobjectRequired
Child attributes
impressionsnumber | any | nullRequired
reactionsnumber | any | nullRequired
commentsnumber | any | nullRequired
repostsnumber | any | nullRequired
quotesnumber | any | nullRequired
savesnumber | any | nullRequired
clicksnumber | any | nullRequired
profileClicksnumber | any | nullRequired
videoViewsnumber | any | nullRequired
engagementRatenumber | any | nullRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/posts/{groupId}/metrics
curl -X GET "https://orbitwrite.com/api/v1/posts/{groupId}/metrics" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "groupId": "string",
  "historyDays": 0,
  "channels": [
    {
      "accountId": "string",
      "postId": "string",
      "platform": "string",
      "channel": "string",
      "points": [
        {
          "fetchedAt": "string",
          "metrics": {
            "impressions": 0,
            "reactions": 0,
            "comments": 0,
            "reposts": 0,
            "quotes": 0,
            "saves": 0,
            "clicks": 0,
            "profileClicks": 0,
            "videoViews": 0,
            "engagementRate": 0
          }
        }
      ]
    }
  ]
}

How one post compares with the others#

GET/api/v1/posts/{groupId}/benchmark

Per channel of the post: its latest engagements, impressions and engagement rate against the channel's other posts published in the 90 days before it, every channel on the same network, and posts of the same format. Peers are read as they stood at this post's age, so a two-day-old post is measured against other posts at two days, and peers too young to have reached that age are left out. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Path parameters

groupIdstringRequired

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200PostBenchmark

The post against its peers

Response attributes
groupIdstringRequired
historyDaysinteger | any | nullRequired

Set when the post is older than the plan's analytics history reaches: it then has no comparisons.

lookbackDaysintegerRequired
channelsobject[]Required
Child attributes
accountIdstringRequired
postIdstringRequired
platformstringRequired
channelstring | any | nullRequired
publishedAtstring | any | nullRequired
ageDaysinteger | any | nullRequired
formatobject | any | nullRequired
Child attributes
keystringRequired
labelstringRequired
metricsobjectRequired
Child attributes
engagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
engagementRatenumber | any | nullRequired
comparisonsobject[]Required
Child attributes
scopestringRequired

channel: this channel's other posts. platform: every channel of the workspace on the same network (only when there is more than one). format: this channel's posts of the same format.

  • "channel"
  • "platform"
  • "format"
postsintegerRequired
fewbooleanRequired

Too few peers for the comparison to say much.

engagementsobject | any | nullRequired
Child attributes
valuenumberRequired

This post's number.

peersintegerRequired

Peer posts that reported this number.

mediannumberRequired
averagenumberRequired
percentileintegerRequired

The share of those peers this post did better than, 0–100 (ties count half).

valuesnumber[]Required

The peers' own numbers, lowest first, for drawing where this post sits. Evenly thinned to at most 120.

impressionsobject | any | nullRequired
Child attributes
valuenumberRequired

This post's number.

peersintegerRequired

Peer posts that reported this number.

mediannumberRequired
averagenumberRequired
percentileintegerRequired

The share of those peers this post did better than, 0–100 (ties count half).

valuesnumber[]Required

The peers' own numbers, lowest first, for drawing where this post sits. Evenly thinned to at most 120.

engagementRateobject | any | nullRequired
Child attributes
valuenumberRequired

This post's number.

peersintegerRequired

Peer posts that reported this number.

mediannumberRequired
averagenumberRequired
percentileintegerRequired

The share of those peers this post did better than, 0–100 (ties count half).

valuesnumber[]Required

The peers' own numbers, lowest first, for drawing where this post sits. Evenly thinned to at most 120.

400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/posts/{groupId}/benchmark
curl -X GET "https://orbitwrite.com/api/v1/posts/{groupId}/benchmark" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "groupId": "string",
  "historyDays": 0,
  "lookbackDays": 0,
  "channels": [
    {
      "accountId": "string",
      "postId": "string",
      "platform": "string",
      "channel": "string",
      "publishedAt": "string",
      "ageDays": 0,
      "format": {
        "key": "string",
        "label": "string"
      },
      "metrics": {
        "engagements": 0,
        "impressions": 0,
        "engagementRate": 0
      },
      "comparisons": [
        {
          "scope": "channel",
          "posts": 0,
          "few": true,
          "engagements": {
            "value": 0,
            "peers": 0,
            "median": 0,
            "average": 0,
            "percentile": 0,
            "values": [
              0
            ]
          },
          "impressions": {
            "value": 0,
            "peers": 0,
            "median": 0,
            "average": 0,
            "percentile": 0,
            "values": [
              0
            ]
          },
          "engagementRate": {
            "value": 0,
            "peers": 0,
            "median": 0,
            "average": 0,
            "percentile": 0,
            "values": [
              0
            ]
          }
        }
      ]
    }
  ]
}

A campaign's report#

GET/api/v1/campaigns/{campaignId}/report

What a campaign published and how it did: totals, its best platform and format (by average engagements per post), its top posts, its longest thread, what stood out against each channel's usual, and every published post with its numbers. Membership decides what counts, not dates, so a post that went out after the campaign ended is still in it. A null metric means nothing reported it, never zero. Requires analytics:read and campaign:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Path parameters

campaignIdstringRequired

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200CampaignReport

The campaign's report

Response attributes
campaignobjectRequired
Child attributes
idstringRequired
titlestring | any | nullRequired
startDatestring | any | nullRequired
endDatestring | any | nullRequired
statusstringRequired

From its dates: scheduled before it starts (or with no start date), active while it runs (no end date means it runs on), completed once its end date has passed.

  • "scheduled"
  • "active"
  • "completed"
durationDaysinteger | any | nullRequired

Days from its start (or first post) to its end, or to now while it runs.

timezonestringRequired
historyDaysinteger | any | nullRequired

Set when the plan's analytics history doesn't reach the campaign's earliest posts: they are listed, but their numbers are left out.

minPostsintegerRequired
publishedobjectRequired
Child attributes
postsintegerRequired

Posts that went out, one per post however many channels it reached.

channelPostsintegerRequired

The same posts counted once per channel.

measuredintegerRequired

Posts with engagement numbers from at least one channel.

firstAtstring | any | nullRequired
lastAtstring | any | nullRequired
activeDaysintegerRequired

Distinct days, in timezone, that something went out.

channelsobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
postsintegerRequired
pipelineobject | any | nullRequired

Posts that haven't gone out: scheduled, and everything else (drafts, awaiting approval). Null without post:read.

Child attributes
scheduledintegerRequired
unscheduledintegerRequired
totalsAnalyticsMetricValuesRequired

Each metric summed; null where nothing reported it (Bluesky has no impressions, say), never 0 for 'unknown'.

Child attributes
engagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
reactionsnumber | any | nullRequired
commentsnumber | any | nullRequired
repostsnumber | any | nullRequired
quotesnumber | any | nullRequired
savesnumber | any | nullRequired
clicksnumber | any | nullRequired
profileClicksnumber | any | nullRequired
videoViewsnumber | any | nullRequired
engagementsPerPostnumber | any | nullRequired
platformsCampaignBreakdownRow[]Required

Best first by average engagements per post, rows too small to judge last.

Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
engagementsnumber | any | nullRequired
avgEngagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
engagementRatenumber | any | nullRequired
fewbooleanRequired

Fewer than minPosts posts: too few for the average to be a finding.

formatsCampaignBreakdownRow[]Required
Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
engagementsnumber | any | nullRequired
avgEngagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
engagementRatenumber | any | nullRequired
fewbooleanRequired

Fewer than minPosts posts: too few for the average to be a finding.

bestPlatformCampaignBreakdownRow | any | nullRequired

The leading platform, preferring one with at least minPosts posts. Check few before calling it a finding.

Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
engagementsnumber | any | nullRequired
avgEngagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
engagementRatenumber | any | nullRequired
fewbooleanRequired

Fewer than minPosts posts: too few for the average to be a finding.

bestFormatCampaignBreakdownRow | any | nullRequired
Child attributes
keystringRequired
labelstringRequired
postsintegerRequired
engagementsnumber | any | nullRequired
avgEngagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
engagementRatenumber | any | nullRequired
fewbooleanRequired

Fewer than minPosts posts: too few for the average to be a finding.

topPostsCampaignReportPost[]Required

Up to 5 posts, best first by engagements; a post with none never ranks.

Child attributes
groupIdstring | any | nullRequired
postIdsstring[]Required
textstringRequired
publishedAtstringRequired
channelsobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
platformUrlstring | any | nullRequired
mediaobject | any | nullRequired
Child attributes
urlstringRequired
mimeTypestringRequired
countintegerRequired
formatobjectRequired
Child attributes
keystringRequired
labelstringRequired
threadLengthintegerRequired

Posts in its longest channel's thread; 1 for a single post.

metricsAnalyticsMetricValuesRequired

Each metric summed; null where nothing reported it (Bluesky has no impressions, say), never 0 for 'unknown'.

Child attributes
engagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
reactionsnumber | any | nullRequired
commentsnumber | any | nullRequired
repostsnumber | any | nullRequired
quotesnumber | any | nullRequired
savesnumber | any | nullRequired
clicksnumber | any | nullRequired
profileClicksnumber | any | nullRequired
videoViewsnumber | any | nullRequired
longestThreadCampaignReportPost | any | nullRequired

The post with the longest thread; null when nothing was a thread.

Child attributes
groupIdstring | any | nullRequired
postIdsstring[]Required
textstringRequired
publishedAtstringRequired
channelsobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
platformUrlstring | any | nullRequired
mediaobject | any | nullRequired
Child attributes
urlstringRequired
mimeTypestringRequired
countintegerRequired
formatobjectRequired
Child attributes
keystringRequired
labelstringRequired
threadLengthintegerRequired

Posts in its longest channel's thread; 1 for a single post.

metricsAnalyticsMetricValuesRequired

Each metric summed; null where nothing reported it (Bluesky has no impressions, say), never 0 for 'unknown'.

Child attributes
engagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
reactionsnumber | any | nullRequired
commentsnumber | any | nullRequired
repostsnumber | any | nullRequired
quotesnumber | any | nullRequired
savesnumber | any | nullRequired
clicksnumber | any | nullRequired
profileClicksnumber | any | nullRequired
videoViewsnumber | any | nullRequired
insightsobject[]Required
Child attributes
idstringRequired
dimensionstringRequired
groupstringRequired
directionstringRequired
  • "better"
  • "worse"
metricstringRequired
titlestringRequired
summarystringRequired
liftnumberRequired
postsintegerRequired
comparedPostsintegerRequired
confidencestringRequired
  • "low"
  • "medium"
  • "high"
exampleGroupIdsstring[]Required
postsCampaignReportPost[]Required

Every published post, newest first, at most 200.

Child attributes
groupIdstring | any | nullRequired
postIdsstring[]Required
textstringRequired
publishedAtstringRequired
channelsobject[]Required
Child attributes
accountIdstringRequired
platformstringRequired
channelstring | any | nullRequired
platformUrlstring | any | nullRequired
mediaobject | any | nullRequired
Child attributes
urlstringRequired
mimeTypestringRequired
countintegerRequired
formatobjectRequired
Child attributes
keystringRequired
labelstringRequired
threadLengthintegerRequired

Posts in its longest channel's thread; 1 for a single post.

metricsAnalyticsMetricValuesRequired

Each metric summed; null where nothing reported it (Bluesky has no impressions, say), never 0 for 'unknown'.

Child attributes
engagementsnumber | any | nullRequired
impressionsnumber | any | nullRequired
reactionsnumber | any | nullRequired
commentsnumber | any | nullRequired
repostsnumber | any | nullRequired
quotesnumber | any | nullRequired
savesnumber | any | nullRequired
clicksnumber | any | nullRequired
profileClicksnumber | any | nullRequired
videoViewsnumber | any | nullRequired
truncatedbooleanRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/campaigns/{campaignId}/report
curl -X GET "https://orbitwrite.com/api/v1/campaigns/{campaignId}/report" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "campaign": {
    "id": "string",
    "title": "string",
    "startDate": "string",
    "endDate": "string",
    "status": "scheduled",
    "durationDays": 0
  },
  "timezone": "string",
  "historyDays": 0,
  "minPosts": 0,
  "published": {
    "posts": 0,
    "channelPosts": 0,
    "measured": 0,
    "firstAt": "string",
    "lastAt": "string",
    "activeDays": 0,
    "channels": [
      {
        "accountId": "string",
        "platform": "string",
        "channel": "string",
        "posts": 0
      }
    ]
  },
  "pipeline": {
    "scheduled": 0,
    "unscheduled": 0
  },
  "totals": {
    "engagements": 0,
    "impressions": 0,
    "reactions": 0,
    "comments": 0,
    "reposts": 0,
    "quotes": 0,
    "saves": 0,
    "clicks": 0,
    "profileClicks": 0,
    "videoViews": 0
  },
  "engagementsPerPost": 0,
  "platforms": [
    {
      "key": "string",
      "label": "string",
      "posts": 0,
      "engagements": 0,
      "avgEngagements": 0,
      "impressions": 0,
      "engagementRate": 0,
      "few": true
    }
  ],
  "formats": [
    {
      "key": "string",
      "label": "string",
      "posts": 0,
      "engagements": 0,
      "avgEngagements": 0,
      "impressions": 0,
      "engagementRate": 0,
      "few": true
    }
  ],
  "bestPlatform": {
    "key": "string",
    "label": "string",
    "posts": 0,
    "engagements": 0,
    "avgEngagements": 0,
    "impressions": 0,
    "engagementRate": 0,
    "few": true
  },
  "bestFormat": {
    "key": "string",
    "label": "string",
    "posts": 0,
    "engagements": 0,
    "avgEngagements": 0,
    "impressions": 0,
    "engagementRate": 0,
    "few": true
  },
  "topPosts": [
    {
      "groupId": "string",
      "postIds": [
        "string"
      ],
      "text": "string",
      "publishedAt": "string",
      "channels": [
        {
          "accountId": "string",
          "platform": "string",
          "channel": "string",
          "platformUrl": "string"
        }
      ],
      "media": {
        "url": "string",
        "mimeType": "string",
        "count": 0
      },
      "format": {
        "key": "string",
        "label": "string"
      },
      "threadLength": 0,
      "metrics": {
        "engagements": 0,
        "impressions": 0,
        "reactions": 0,
        "comments": 0,
        "reposts": 0,
        "quotes": 0,
        "saves": 0,
        "clicks": 0,
        "profileClicks": 0,
        "videoViews": 0
      }
    }
  ],
  "longestThread": {
    "groupId": "string",
    "postIds": [
      "string"
    ],
    "text": "string",
    "publishedAt": "string",
    "channels": [
      {
        "accountId": "string",
        "platform": "string",
        "channel": "string",
        "platformUrl": "string"
      }
    ],
    "media": {
      "url": "string",
      "mimeType": "string",
      "count": 0
    },
    "format": {
      "key": "string",
      "label": "string"
    },
    "threadLength": 0,
    "metrics": {
      "engagements": 0,
      "impressions": 0,
      "reactions": 0,
      "comments": 0,
      "reposts": 0,
      "quotes": 0,
      "saves": 0,
      "clicks": 0,
      "profileClicks": 0,
      "videoViews": 0
    }
  },
  "insights": [
    {
      "id": "string",
      "dimension": "string",
      "group": "string",
      "direction": "better",
      "metric": "string",
      "title": "string",
      "summary": "string",
      "lift": 0,
      "posts": 0,
      "comparedPosts": 0,
      "confidence": "low",
      "exampleGroupIds": [
        "string"
      ]
    }
  ],
  "posts": [
    {
      "groupId": "string",
      "postIds": [
        "string"
      ],
      "text": "string",
      "publishedAt": "string",
      "channels": [
        {
          "accountId": "string",
          "platform": "string",
          "channel": "string",
          "platformUrl": "string"
        }
      ],
      "media": {
        "url": "string",
        "mimeType": "string",
        "count": 0
      },
      "format": {
        "key": "string",
        "label": "string"
      },
      "threadLength": 0,
      "metrics": {
        "engagements": 0,
        "impressions": 0,
        "reactions": 0,
        "comments": 0,
        "reposts": 0,
        "quotes": 0,
        "saves": 0,
        "clicks": 0,
        "profileClicks": 0,
        "videoViews": 0
      }
    }
  ],
  "truncated": true
}

Orb AI's summary of a campaign#

GET/api/v1/campaigns/{campaignId}/summary

The stored summary, if one has been written, and whether it has fallen behind the campaign's numbers. Never calls a model. Requires analytics:read and campaign:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Path parameters

campaignIdstringRequired

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200CampaignSummaryRead

The stored summary

Response attributes
summaryCampaignSummary | any | nullRequired
Child attributes
textstringRequired
statusstringRequired

The phase it was written for: active is a running campaign's update, completed the analysis once it ended.

  • "scheduled"
  • "active"
  • "completed"
generatedAtstringRequired
stalebooleanRequired

The campaign's numbers or phase have moved since it was written.

summarisablebooleanRequired

Whether a summary can be written now: AI is available and at least one of the campaign's posts has numbers.

duebooleanRequired

Summarisable, and there is no summary for the phase the campaign is in. A page may write one unasked (ifMissing).

401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/campaigns/{campaignId}/summary
curl -X GET "https://orbitwrite.com/api/v1/campaigns/{campaignId}/summary" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "summary": {
    "text": "string",
    "status": "scheduled",
    "generatedAt": "string",
    "stale": true
  },
  "summarisable": true,
  "due": true
}

Write Orb AI's summary of a campaign#

POST/api/v1/campaigns/{campaignId}/summary

Writes a short summary from the campaign's report and stores it: an update while the campaign runs, an analysis once it has ended. Spends one AI assist. With ifMissing, returns the stored summary instead when there is one for the campaign's current phase. Requires analytics:read and campaign:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Path parameters

campaignIdstringRequired

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Body application/json

ifMissingboolean

Only write one when the campaign has no summary for the phase it is in.

Responses

200object

The summary, and whether it was written just now

Response attributes
summaryCampaignSummaryRequired
Child attributes
textstringRequired
statusstringRequired

The phase it was written for: active is a running campaign's update, completed the analysis once it ended.

  • "scheduled"
  • "active"
  • "completed"
generatedAtstringRequired
stalebooleanRequired

The campaign's numbers or phase have moved since it was written.

generatedbooleanRequired
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
422Error

Nothing to summarise yet: none of the campaign's posts have numbers

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
500Error

No AI provider configured

Response attributes
errorstringRequired
issuesany[]
502Error

An upstream provider failed

Response attributes
errorstringRequired
issuesany[]
POST /api/v1/campaigns/{campaignId}/summary
curl -X POST "https://orbitwrite.com/api/v1/campaigns/{campaignId}/summary" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ifMissing": true
}'
Response
{
  "summary": {
    "text": "string",
    "status": "scheduled",
    "generatedAt": "string",
    "stale": true
  },
  "generated": true
}

Orb AI's analysis of the last 28 days#

GET/api/v1/analytics/summary

The stored analysis of the workspace's last 28 days, if one has been written, and whether a new one is due (one a day at most). Never calls a model. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200object

The stored analysis

Response attributes
summaryWorkspaceAnalyticsSummary | any | nullRequired
Child attributes
textstringRequired
fromstringRequired

Start of the window it was written about.

tostringRequired
generatedAtstringRequired
nextAtstring | any | nullRequired

When it may next be rewritten (once a day at most); null when it may be now.

duebooleanRequired

No analysis yet, or the last one is a day old, and something was published in the last 28 days: a page may write one unasked.

availablebooleanRequired

Whether this deployment can write one at all (an AI provider is configured).

401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/analytics/summary
curl -X GET "https://orbitwrite.com/api/v1/analytics/summary" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "summary": {
    "text": "string",
    "from": "string",
    "to": "string",
    "generatedAt": "string",
    "nextAt": "string"
  },
  "due": true,
  "available": true
}

Write Orb AI's analysis of the last 28 days#

POST/api/v1/analytics/summary

Writes a short analysis of the workspace's last 28 days against the 28 before, and stores it. At most once a day: within a day of the last one, this returns that one (generated: false) and spends nothing. Otherwise spends one AI assist. Requires analytics:read.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200object

The analysis, and whether it was written just now

Response attributes
summaryWorkspaceAnalyticsSummaryRequired
Child attributes
textstringRequired
fromstringRequired

Start of the window it was written about.

tostringRequired
generatedAtstringRequired
nextAtstring | any | nullRequired

When it may next be rewritten (once a day at most); null when it may be now.

generatedbooleanRequired
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
422Error

Nothing to analyse: no posts with numbers in the last 28 days

Response attributes
errorstringRequired
issuesany[]
429Error

Rate limit exceeded

Response attributes
errorstringRequired
issuesany[]
500Error

No AI provider configured

Response attributes
errorstringRequired
issuesany[]
502Error

An upstream provider failed

Response attributes
errorstringRequired
issuesany[]
POST /api/v1/analytics/summary
curl -X POST "https://orbitwrite.com/api/v1/analytics/summary" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "summary": {
    "text": "string",
    "from": "string",
    "to": "string",
    "generatedAt": "string",
    "nextAt": "string"
  },
  "generated": true
}