Guides
Publishing a post
A single POST /v1/posts fans out to every account you list and returns one normalised Post object. This page covers the whole request and response in depth.
The request body
| Field | Type | Description |
|---|---|---|
text | string | The post text. Used for every account unless overridden. Required unless every account is overridden. |
accounts | array<string> | Required. The social-account ids to publish to (e.g. acc_123). Get them from GET /v1/social-accounts. |
textOverrides | object | Per-platform text, keyed by platform (e.g. { "x": "shorter" }). Falls back to text. |
media | array<string> | Media ids from POST /v1/media to attach to every account. |
mediaOverrides | object | Per-platform media sets, keyed by platform. |
scheduledAt | string<date-time> | ISO-8601 UTC. Omit to publish now. See Scheduling. |
platformOptions | object | Platform-specific options (Pinterest boardId, TikTok mode, …). See below. |
Per-platform text & options
The same post rarely reads the same on every network. Two hooks let you tailor per platform without sending separate requests:
textOverrides— a shorter caption for X, a longer one for LinkedIn, keyed by platform. Anything not overridden usestext.platformOptions— platform-specific settings, e.g. a PinterestboardId+ destinationlink, or a TikTokmode(directto publish now,inboxto send to drafts). CallGET /v1/platforms/{platform}for the full, live option set.
{
"text": "Big update on the blog 👇",
"textOverrides": { "x": "Big update — link in thread" },
"accounts": ["acc_x", "acc_pin"],
"platformOptions": { "pinterest": { "boardId": "98765", "link": "https://example.com/post" } }
}The response & its lifecycle
You get one Post with an overall state and a targets array — one entry per account, each with its own state, url and (if it failed) an error.
{
"id": "post_a1b2c3",
"state": "partial",
"targets": [
{ "account": "acc_x", "platform": "x", "state": "published", "url": "https://x.com/…" },
{ "account": "acc_pin", "platform": "pinterest", "state": "failed",
"error": { "type": "invalid_request", "message": "boardId not found", "retryable": false } }
]
}A post's state summarises its targets:
| State | Meaning |
|---|---|
queued | Accepted, not yet sent. |
processing | Being published (some platforms publish asynchronously). |
published | Every target succeeded. |
partial | Some targets published, some failed — check each target. |
failed | No target published. |
scheduled | Queued for a future scheduledAt. |
Partial success is normal — and free where it fails
One target failing (over a limit, a bad option) never blocks the others. You're only charged for targets that actually publish — failed targets cost nothing. Always read each targets[].state rather than assuming the whole post succeeded. Full detail on the Errors page.
Idempotency
Send an Idempotency-Key header (any unique string per logical post). If the request is retried with the same key — a network blip, an agent re-run — PostLake returns the original result instead of posting again. This is what makes automated and agent-driven posting safe.
curl -X POST https://api.postlake.dev/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: post-2026-08-01-blog" \
-H "Content-Type: application/json" \
-d '{ "text": "Hello", "accounts": ["acc_123"] }'