POST /posts
Creates a post for one or more accounts. MCP tool: create_post. Background: Approvals, Scheduling, First comments and threads.
curl -X POST https://botdoor.co/api/v1/posts -H "Authorization: Bearer $BOTDOOR_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: launch-2026-10-12" \ -d '{"content": "Fresh sourdough at 7am", "accountIds": ["2ae8…"], "mediaIds": ["9b1c…"], "scheduledFor": "2026-10-12T07:00:00+11:00", "firstComment": "Order: https://acme.example", "dryRun": true}'Fields
Section titled “Fields”| Field | Type | Notes |
|---|---|---|
accountIds |
string[] | Required. Ids from /accounts |
content |
string | Post text |
mediaIds |
string[] | From /media. Several make a carousel. mediaId (one string) also works |
dryRun |
boolean | Validate only. Nothing is stored, sent or counted |
scheduledFor |
string | ISO 8601 with offset, under a year ahead. Omit to post now |
requireApproval |
boolean | true always waits. false can’t skip an asks-first key |
firstComment |
string | Reply from the same account about 10 s after it’s live. Put links here |
thread |
string[] | Follow-up posts (X, Threads, Bluesky). thread[0] is the first reply |
firstReply |
string | Alias for a one-item thread. Not with thread |
poll |
object | {options, durationMinutes, question}. X or LinkedIn, no media. question is LinkedIn only |
cover |
object | Video cover: {mediaId} (image upload) or {offsetMs} (frame) |
tiktokAiGenerated |
boolean | TikTok: label as AI-generated |
Header Idempotency-Key: a retry with the same key never posts twice.
Responses
Section titled “Responses”200dry run:{"dryRun": true, "valid": true, "warnings": [], "requireApproval": true, "wouldPost": {…}}201created:{"post": {…}}withstatusneeds_approval,scheduled,publishing,publishedorfailed. Shape: GET /posts/{id}.200withreplayed: true: sameIdempotency-Keyas an earlier post.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | validation_failed |
A field is missing or malformed. See details.fields |
| 400 | invalid_date, scheduled_in_past, scheduled_too_far |
Bad scheduledFor |
| 400 | media_required |
Instagram, TikTok, YouTube and Pinterest need media |
| 400 | thread_unsupported, poll_unsupported, first_comment_unsupported, cover_unsupported |
A target can’t do that |
| 400 | tiktok_privacy_conflict, tiktok_ai_unsupported |
TikTok settings conflict |
| 400 | upstream_rejected |
The network refused the content |
| 403 | plan_feature_unavailable, post_limit_reached |
Plan rule (X, 30 posts a month on Free) |
| 404 | account_not_found, media_not_found |
An id isn’t in this workspace |
| 409 | no_accounts_connected, account_needs_reconnection |
Connect or reconnect first |
| 409 | media_expired |
Upload is over 7 days old |
| 409 | duplicate_content |
Same text was just posted to that account |
| 409 | tiktok_cannot_post |
TikTok says the creator can’t post now. Retryable |
| 422 | idempotency_key_reused |
Same key, different body |
Common questions
How do I check a post without publishing it?
Send dryRun: true. You get valid, warnings and exactly what would be posted, and it doesn't count toward the monthly limit.
Why did my post stop at needs_approval?
Your key asks first, or you sent requireApproval: true. A signed-in person approves it in the app.
Can I post to several networks at once?
Yes. Put several accountIds in one post. Each target gets its own status.
Where should links go?
In firstComment, so the main post isn't penalised for a link and the link sits right under it.
Is scheduledFor in my timezone?
It's whatever offset you send. Always include one, such as +11:00 or Z.