Skip to main content
Publishing is one HTTP call. The same POST /v1/posts endpoint handles immediate, scheduled, and multi-target publishes; the response status code and the lifecycle differ.

Immediate publish

A request body without scheduledAt runs synchronously. Preflight runs, the upstream platform call fires, and the response is 200 with a batch envelope:
immediate.sh
Response:
200.json
The top-level 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 to targets[] 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
Each target can override 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-level status summarizes the per-target outcomes: The batch itself returns 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 as 400 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 omit accountId and let the API resolve it:
platform-only.json
When two or more accounts match, you get validation_failed with rule: "target.account.ambiguous" and the candidate ids in remediation — pass an explicit accountId to disambiguate.

Scheduled publish

Add scheduledAt and every target in the batch goes onto the publish queue. Response is 202 Accepted with status: "queued":
scheduled.sh
See schedule a post for the full lifecycle and the v1 text-only constraint.

What to subscribe to

Webhooks fire per target, not per batch. A successful immediate publish on three targets fires three post.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.