POST /v1/posts endpoint handles immediate, scheduled, and multi-target publishes; the response status code and the lifecycle differ.
Immediate publish
A request body withoutscheduledAt runs synchronously. Preflight runs, the upstream platform call fires, and the response is 200 with a batch envelope:
immediate.sh
200.json
id is the batch id; each entry in results[] is one target’s outcome. The uri shape is platform-native (AT Proto for Bluesky, Twitter snowflake id, LinkedIn URN, etc.) — save it to deep-link to the post later, or save results[].postId to fetch the record back via GET /v1/posts/:id.
Multi-target fan-out (the headline)
Add more entries totargets[] and one request publishes to N accounts. The cap is 25 targets per request — beyond that you get validation_failed with rule: "targets.max".
multi-target.sh
text, media, firstComment, and per-platform options independently — anything you omit on a target inherits from the top-level defaults. Mix per-target overrides to send tailored copy in one call:
per-target-overrides.json
Batch status semantics
The top-levelstatus summarizes the per-target outcomes:
status | when |
|---|---|
published | every target succeeded |
partial_failed | at least one succeeded, at least one failed |
failed | every target failed |
queued | scheduled batch accepted (returned with 202) |
200 even on partial_failed — per-target errors live inside results[].error. Loop the array and branch on each entry’s status; a mixed batch is a normal, expected shape, not an exception path.
Atomic shape preflight
Cheap shape checks (text length, media counts + exclusivity, alt-text length, per-platform option sanity) run atomically across the batch before any persistence: if any target fails a shape rule the whole batch is rejected as400 preflight_failed and nothing is published. Deep checks (URL reachability, MIME sniffing, byte caps) run inside each publisher and surface per-target — a deep-check failure lands as partial_failed, not a 400.
Auto-resolution by platform
If your org has exactly one connected account for a platform, you can omitaccountId and let the API resolve it:
platform-only.json
validation_failed with rule: "target.account.ambiguous" and the candidate ids in remediation — pass an explicit accountId to disambiguate.
Scheduled publish
AddscheduledAt and every target in the batch goes onto the publish queue. Response is 202 Accepted with status: "queued":
scheduled.sh
What to subscribe to
Webhooks fire per target, not per batch. A successful immediate publish on three targets fires threepost.published events. A scheduled batch fires post.queued per target on accept, then post.published / post.rejected / post.failed per target when the worker runs. Subscribe via POST /v1/webhook-endpoints and verify signatures per webhooks.
Idempotency
Idempotency-Key applies to the whole batch. Reuse the same key on a retry and you get the original CreatePostResponse (same batch id, same per-target results) replayed verbatim — no double publishing on any target. Change any byte of the body (different targets[], different text, anything) and you get idempotency_conflict. See idempotency for the full contract.
When it fails
The error envelope is the same shape across every failure mode —code, message, rule, platformResponse, remediation, docUrl, ruleUrl, requestId. See errors for the eleven codes.
See also
Schedule posts
Defer publishes via
scheduledAt.Upload media
Three media-source modes; when to use each.
Connect accounts
OAuth flows per platform.
Webhooks
Stop polling; subscribe instead.

