Check the result
Understand post statuses and find out if a post was published.
Publishing happens in the background. When you create a post, Bulkit answers right away, before the social networks have received anything. The real result comes later — minutes later, or days later for a scheduled post.
You can get the result in two ways:
- Webhooks (recommended) — Bulkit calls your server when something happens. See Webhooks.
- Polling — you call
GET /posts/{id}from time to time.
Get one post
curl "$BULKIT_URL/posts/POST_ID" -H "x-api-key: $BULKIT_API_KEY"{
"id": "clx3k2p9q0001abcd1234efgh",
"status": "partially_published",
"scheduledAt": "2026-10-15T09:00:00.000Z",
"publishedAt": null,
"channels": [
{
"channelId": "chn_x",
"platform": "x",
"status": "published",
"error": null,
"remoteUrl": "https://x.com/acme/status/123",
"blocks": [{ "content": "Shipping today.", "remoteUrl": "https://x.com/acme/status/123" }]
},
{
"channelId": "chn_li",
"platform": "linkedin",
"status": "failed",
"error": "LinkedIn refused the post: the access token has expired.",
"remoteUrl": null,
"blocks": [{ "content": "A longer version, for LinkedIn.", "remoteUrl": null }]
}
]
}The top status is a summary. The real result is in each channel. Look at
channels[].status and channels[].error.
What each status means
| Status | Meaning |
|---|---|
draft | Saved. Nothing will be published until you publish it. |
scheduled | Waiting for scheduledAt. |
publishing | Being sent to the platform right now. |
published | Live. remoteUrl has the link. |
partially_published | (top level only) Some channels worked, some failed. |
failed | Did not go out. error says why. |
- The top-level
publishedAtis only set when every channel is published. So if it is notnull, the whole post is done. - In a thread, each block has its own
remoteUrl.
Fix a failed post
- Read
channels[].errorto see what went wrong. - Fix the cause (for example, reconnect the account in the app).
- Call
POST /posts/{id}/publish.
curl -X POST "$BULKIT_URL/posts/POST_ID/publish" \
-H "x-api-key: $BULKIT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"This only retries the channels that are not published yet. The ones that worked are not posted again.
List your posts
curl "$BULKIT_URL/posts?status=failed&limit=50" -H "x-api-key: $BULKIT_API_KEY"| Query | What it does |
|---|---|
status | Filter by status. Repeat it for more: ?status=scheduled&status=failed. |
tag | Filter by tag id. Repeat it for more. |
from, to | Only posts scheduled in this time range. A date (2026-10-15) or a full ISO 8601 time both work. |
limit | Posts per page. Default 25, max 100. |
cursor | The nextCursor from the previous page. |
The response is { "data": [...], "nextCursor": "..." }. To get the next
page, send ?cursor= with the nextCursor value. Stop when nextCursor is
null.
let cursor = null;
do {
const url = new URL(`${process.env.BULKIT_URL}/posts`);
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { "x-api-key": process.env.BULKIT_API_KEY } });
const page = await res.json();
for (const post of page.data) console.log(post.id, post.status);
cursor = page.nextCursor;
} while (cursor);Engagement numbers
GET /posts/{id}/metrics returns likes, comments, views and more, as the
platform reports them over time.
- Numbers are running totals, so they only go up (unless people unlike).
- Readings are hourly on day one, then daily for a week, then weekly up to 30 days. After that they stop.
- If a metric is missing, the platform did not report it. It does not mean zero.
- Some channels report nothing. Check
metricsStatusonGET /channels.
For totals across many posts, use GET /stats?from=…&to=….