# POST /posts

> Create, schedule, or dry-run a social post across connected accounts with Botdoor. Every field, response and error.

Source: https://docs.botdoor.co/api/posts-create/

Creates a post for one or more accounts. MCP tool: `create_post`. Background: [Approvals](https://docs.botdoor.co/concepts/approvals/), [Scheduling](https://docs.botdoor.co/concepts/scheduling/), [First comments and threads](https://docs.botdoor.co/concepts/first-comments-and-threads/).

```bash
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

| Field | Type | Notes |
|---|---|---|
| `accountIds` | string[] | Required. Ids from [/accounts](https://docs.botdoor.co/api/accounts/) |
| `content` | string | Post text |
| `mediaIds` | string[] | From [/media](https://docs.botdoor.co/api/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

- `200` dry run: `{"dryRun": true, "valid": true, "warnings": [], "requireApproval": true, "wouldPost": {…}}`
- `201` created: `{"post": {…}}` with `status` `needs_approval`, `scheduled`, `publishing`, `published` or `failed`. Shape: [GET /posts/{id}](https://docs.botdoor.co/api/posts-read/).
- `200` with `replayed: true`: same `Idempotency-Key` as an earlier post.

## 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.
