Sending a post
Publish or schedule a post you already created, one at a time or a whole campaign, without waiting for the platform.
https://antwork.io/api/v1/posts/{id}/publishScopepublishPublishing now
POST /api/v1/posts/{id}/publish sends a stored post, typically a draft made with draft: true or posts[]. It is committed with a delivery time of now and queued; the answer is a 202 with the post, and nothing waits for the platform. Poll GET /api/v1/posts/{id} until status is published or failed. Needs the publish scope. Send an Idempotency-Key: a retried request must not send the post twice.
curl -X POST https://antwork.io/api/v1/posts/pQ7x.../publish \
-H "Authorization: Bearer $ANTWORK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"object": "post",
"id": "pQ7x...",
"status": "scheduled",
"scheduledFor": "2026-11-05T10:00:00.000Z",
"readiness": { "characterCount": 112, "characterLimit": 280, "overLimit": false,
"mediaRequired": false, "settingsRequired": false, "publishBlocker": null },
"editUrl": "https://antwork.io/workspace/ws_.../compose?postId=pQ7x...",
"retried": false,
...
}A post whose publish failed is retried instead: the platform's error is cleared and it is queued a minute out, with retried: true. The same thing the MCP publish_post tool does with a failed post.
Scheduling
POST /api/v1/posts/{id}/schedule with {"scheduledFor": "..."} (ISO 8601, in the future, at most 30 days ahead) schedules a stored post through the same checks; a scheduled post moves to the new time. 202, and the post as it now stands. Needs publish.
curl -X POST https://antwork.io/api/v1/posts/pQ7x.../schedule \
-H "Authorization: Bearer $ANTWORK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"scheduledFor": "2026-11-05T10:00:00Z"
}'POST /api/v1/posts/{id}/unschedule moves a scheduled post back to draft and cancels its queued publish, the same as PATCH /api/v1/posts/{id} with status: "draft". It answers 200 with the post at once, and unscheduled: false when it was not scheduled. Only write is needed, since it takes work out of the queue.
When a post is not sent
The reasons are the ones POST /api/v1/posts gives per target. A post that breaks a platform rule is a 400 (text_too_long, media_required, pinterest_board_required…); fix it with PATCH /api/v1/posts/{id} or at its editUrl. When your plan or the account has no room, it is a 409 (quota_exceeded, daily_cap_reached, schedule_queue_full, duplicate_post, posting_too_close). A published post is a 409 post_already_published. Nothing is queued in any of these cases.
{
"error": {
"type": "invalid_request_error",
"code": "daily_cap_reached",
"message": "This X account has reached its daily cap ..."
}
}tiktok_privacy_required until the owner picks it at the post's settingsUrl; after that, these endpoints send it.Campaigns
POST /api/v1/campaigns/{id}/publish sends every post of a campaign (the campaignId a create returns), each on its own: one refused post does not hold the others back, and a failed one is retried. At most 10 posts per campaign. The answer is the create's batch: a post_result per post, 202 when any was accepted, and a 400 with the first reason when none was. Poll each post.
curl -X POST https://antwork.io/api/v1/campaigns/camp-1a2b3c4d5e6f/publish \
-H "Authorization: Bearer $ANTWORK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"object": "post_batch",
"campaignId": "camp-1a2b3c4d5e6f",
"posts": [
{ "object": "post_result", "id": "pQ7x...", "platform": "linkedin", "status": "accepted", "reason": null, ... },
{ "object": "post_result", "id": "pR2k...", "platform": "x", "status": "rejected", "reason": "text_too_long", ... }
]
}POST /api/v1/campaigns/{id}/schedule with {"scheduledFor": "..."} schedules them all at one time. A campaign that is not yours answers 404 campaign_not_found.