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

FieldTypeDescription
textstringThe post text. Used for every account unless overridden. Required unless every account is overridden.
accountsarray<string>Required. The social-account ids to publish to (e.g. acc_123). Get them from GET /v1/social-accounts.
textOverridesobjectPer-platform text, keyed by platform (e.g. { "x": "shorter" }). Falls back to text.
mediaarray<string>Media ids from POST /v1/media to attach to every account.
mediaOverridesobjectPer-platform media sets, keyed by platform.
scheduledAtstring<date-time>ISO-8601 UTC. Omit to publish now. See Scheduling.
platformOptionsobjectPlatform-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:

{
  "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:

StateMeaning
queuedAccepted, not yet sent.
processingBeing published (some platforms publish asynchronously).
publishedEvery target succeeded.
partialSome targets published, some failed — check each target.
failedNo target published.
scheduledQueued 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"] }'