Guides
Scheduling posts
Add a scheduledAt and the post is queued and fires later. Credits are charged when it publishes. Not when you schedule it. Invalid media (an oversized image, a dead connection) is refused at create time, not at 7am. A TikTok PNG is converted to JPEG at create, with a warning.
Schedule a post
Send an ISO-8601 timestamp. UTC with a trailing Z still works. For a wall-clock time in a named zone, pass timezone (IANA) with a naive scheduledAt. The stored fire time is always UTC. The post comes back with state: "scheduled".
{
"text": "Friday recap",
"accounts": ["acc_123"],
"scheduledAt": "2026-08-13T07:00:00",
"timezone": "Europe/London"
}That queues for 07:00 BST (06:00 UTC). The response includes scheduledAt in UTC and scheduledAtLocal in the zone you named. Existing posts that were stored as UTC are unchanged.
A naive datetime without timezone is a 400 unless the account has a default timezone (PATCH /v1/me with { "timezone": "Europe/London" }). "7am" with a trailing Z is 7am UTC, which is 8am in British Summer Time. If the account has a default timezone, a trailing-Z time is still that UTC instant; the zone is stored only so lists can show local time.
Timezones
Prefer timezone plus a naive local time when a person said "7am UK". Convert yourself only when you already have a UTC instant. Set an account default once if every post is in the same zone:
PATCH /v1/me
{ "timezone": "Europe/London" }Then a naive scheduledAt of 2026-08-13T07:00:00 is 07:00 London without repeating the zone on every call.
{
"scheduledAt": "2026-08-13T07:00:00",
"timezone": "Europe/London"
}Z, no timezone) is still the #1 scheduling bug. Posts fire an hour off. The dashboard composer converts for you.Edit or cancel before it fires
While a post is scheduled you can change it or pull it entirely, by id:
| Action | Endpoint |
|---|---|
| Edit text, media, destinations, options, or time | PATCH /v1/posts/{id} |
| Cancel only a scheduled post | POST /v1/posts/{id}/cancel |
PATCH can change text, media, mediaAlt, mediaOverrides, platformOptions, scheduledAt, timezone, and accounts. Pass the complete final list of connected acc_… IDs in accounts to add or remove destinations. Unchanged destinations are preserved. New destinations use the same scheduled time and must pass content validation. Destination edits close 30 seconds before publishing, or as soon as publishing starts. An approval-required agent cannot change an approved post's destinations; the owner can review and make that change in the dashboard. A Profile Group is a selection aid: changing its membership does not alter an existing scheduled post. platformOptions merge per platform, so { "tiktok": { "title": "…" } } does not wipe other TikTok fields. Cancellation retains the original Idempotency-Key receipt. Replaying the same key does not create a replacement. Use a new key only for an intentional new schedule.
When changing accounts on a scheduled post, also send expectedAccounts: the post's current target account IDs from GET /v1/posts/{id} in order. This refuses stale edits instead of overwriting someone else's changes.
GET /v1/posts (and MCP list_posts) accept ?account=acc_… and ?profile=<username> to show one channel or one profile. Unknown values are 404. Scheduled times in list views always include UTC; when a zone is known they read like 2026-08-14 07:00 Europe/London (06:00 UTC).
Managing a large calendar
Bulk-edit preview snapshots cover the full stored posts and the requested patch, including media and platform options edited through single-post PATCH, not just the fields shown in the before/after diff. Any intervening stored change or change to the requested patch requires another preview.
Give related posts the same campaign slug when creating them, for example founder-drop-01. Optional assetMetadata can record short facts such as hook, music track, footage type and source filename on each post. Query GET /v1/posts?campaign=founder-drop-01&state=scheduled&sort=scheduledAt to find the campaign. Add targetCount=single or targetCount=shared to distinguish standalone posts from shared multi-channel schedules.
To change up to 200 scheduled posts together, send their exact IDs to PATCH /v1/posts/bulk with dryRun: true. Inspect the per-post before and after values, then send the same request with dryRun: false and the returned snapshot. Storage changes all together or not at all. accounts replaces the exact destination list on every selected post. shiftMinutes moves each UTC instant by elapsed minutes, not local wall-clock time across daylight-saving changes. Timers are re-armed best-effort after storage changes, with the due-post cron as a backstop.
Add channels without replacing existing destinations
In the dashboard, open Posts, choose Scheduled, then Select all matching scheduled posts. This uses your current profile, network, search and calendar-date filters. It selects up to 1,000 posts across pages, excluding posts already publishing or due within one minute. Choose Add channels, select the exact connected accounts, and choose Preview changes. Review the changes before confirming. If you manage several profiles, filter by the intended profile first.
The dashboard saves sequential batches of up to 100 posts. Each batch is all-or-none, but the whole multi-batch operation is not one transaction. If a later batch fails, earlier confirmed batches stay saved and the remaining selection stays available for review. An uncertain request must be checked before retrying. No publishing credits are spent merely editing schedules; each added destination uses the normal credit rules when it publishes.
API and MCP clients can use patch.addAccounts to add account IDs to each post while preserving its existing destination list, caption, media and time. Already-included accounts are not duplicated. Do not combine addAccounts and accounts. Every final destination must pass content validation; incompatible content or disconnected new accounts reject the batch without changes. Posts must remain wholly scheduled and more than one minute from publishing.
{
"ids": ["post_first", "post_second"],
"patch": { "addAccounts": ["acc_linkedin", "acc_threads"] },
"dryRun": true
}
Send this to PATCH /v1/posts/bulk, or use MCP edit_scheduled_posts_bulk. Apply with the same IDs and patch, dryRun: false, and the returned snapshot. For more than 200 posts, use bounded sequential batches, review every preview, and keep track of confirmed results. No operation silently shortens captions, removes existing channels or republishes past posts.
To create 1 to 200 scheduled posts in one transaction, send their post bodies to POST /v1/posts/calendar. Give each a future scheduledAt, or add a cadence with startAt and everyMinutes to assign elapsed UTC slots in order. Preview with dryRun: true, inspect times and destinations, then apply with the returned snapshot and a unique idempotencyKey. The posts and their receipt are stored together or not at all. A retry with the same key returns the original posts.
Bulk cancellation through POST /v1/posts/bulk/cancel also removes all selected scheduled rows in one transaction. If a post changes or starts publishing, none are cancelled. This is not an atomic swap with a replacement calendar: create and verify the new calendar before cancelling the old one.
To plan a regular local-time cadence, call POST /v1/posts/cadence/preview or MCP preview_cadence with an IANA timezone, local start date and time, day count, and named tracks with optional stagger minutes. It returns exact UTC slots without creating posts, reserving slots, or charging credits. Supply occupiedAt from your existing calendar to avoid those times. The planner rejects nonexistent and repeated daylight-saving local times rather than guessing. Feed its UTC slots into the calendar batch as explicit scheduledAt values. Platform pacing and credits remain authoritative when posts publish.
Use GET /v1/posts?state=scheduled&sort=scheduledAt&profile=<username>&from=<ISO>&to=<ISO>&limit=100 to read the calendar in scheduled-time order. Keep passing nextCursor until it is null. This cursor continues past a cancelled post on an earlier page; the default creation-time cursor does not have that guarantee. Supply explicit timezone-qualified timestamps for from and to.
For a planned batch cancellation, first collect the exact post IDs from the calendar. Then preview it:
POST /v1/posts/bulk/cancel
{
"ids": ["post_abc", "post_def"],
"profile": "my-brand",
"from": "2026-10-01T00:00:00Z",
"to": "2026-11-01T00:00:00Z",
"dryRun": true
}Send the same request with dryRun: false and the returned snapshot to cancel. The snapshot covers every stored field, including media and platform options, plus the selection scope. Up to 200 unique IDs are accepted. All posts must remain scheduled in the requested profile and time window. Any change or publishing race produces HTTP 409 with no cancellation. Success is HTTP 200, never a partial HTTP 207. This is atomic cancellation, not an atomic swap with a replacement calendar. Legacy requests without a snapshot retain execution-time protection only.
Cancel one post without risking retraction
Use POST /v1/posts/{id}/cancel with { "dryRun": true }, then apply with { "snapshot": "RETURNED_SNAPSHOT" }. It only cancels scheduled posts and never retracts published content. The SDK method is posts.cancelScheduled; MCP cancel_post supports the same preview and snapshot, and accepts scoped ids for batch cancellation. Generic DELETE /v1/posts/{id} can retract published content; do not use it for cancel-only integrations.
For regular cadence, generate each timestamp in the intended IANA time zone, validate each post, then create it with a distinct Idempotency-Key. Reuse the same key only when retrying that same create request. Space scheduled times according to the connected platform's limits, and review the returned scheduledAt values before handing the calendar to someone else.
POST /v1/posts/validate returns { "ok": boolean, "targets": [...] }. Each target has account, platform, ok, errors, warnings, and issues (code, message, fix, optional param, and docs). Warnings do not block a target. A malformed request may return HTTP 400 instead of a validation result. Validation does not reserve a future publishing slot or guarantee that a platform will accept a post later.
Schedules with human approval
An approval-required submission stays awaiting_approval, even if it has a requested publish time. It is not armed and cannot fire before a human approves it. Approval preserves the ID and metadata. Approve and schedule keeps the requested future time; approve and publish explicitly clears it. A past requested time must be replaced with a future time or cleared to publish now.
Pending content can be edited and rejected without publishing. After approval, a scheduled post uses the normal scheduled edit and scheduled-only cancel endpoints. Preview cancellation, then send its full-state snapshot on apply. If publishing starts first, cancellation returns 409 and never retracts the publication. Bulk cancellation is all-or-none; generic multi-target deletion/retraction can return partial 207 results and is a different operation.
The uploaded-media policy applies on every plan while the account remains open, including approval-required, free and PAYG integrations. GET /v1/media/policy is the machine-readable contract: uploaded assets have a 90-day minimum retention horizon, with each scheduled reference protected through terminal outcome plus 30 days if later. There is no automatic expiry currently. Cancellation, channel disconnection and subscription cancellation do not delete the uploaded asset. Source URLs are public, including for private destination posts. Keep a durable source yourself for disaster recovery. External URLs are controlled by their host and are not covered by uploaded-media retention. See the retention terms and exceptions.
Disconnecting a scheduled destination
On Channels, choose Disconnect to preview the affected schedules. Nothing changes until you confirm one of these choices:
- Remove from scheduled posts: only this destination is removed. Other destinations keep their schedules. If no destinations remain, the post becomes a draft with its content and media preserved.
- Keep for reconnection: scheduled targets retain the account identity. This destination cannot publish until the same account is reconnected. Other valid destinations can still publish, and a missing destination is not charged.
Published posts stay untouched. PostLake never guesses why an account was disconnected or substitutes another account. For already disconnected targets, open the scheduled post to remove the destination or reconnect the intended account.
API integrations read GET /v1/social-accounts/{id}/disconnect-preview, ask the owner to choose, then call DELETE /v1/social-accounts/{id}?scheduledPosts=remove&expectedSnapshot=.... Use the returned snapshot. The compatibility default is keep; passing its snapshot is recommended. MCP uses disconnect_account with preview: true, then scheduled_posts and expected_snapshot.
Preview runs only on demand. Confirmation checks full post state and publishing claims in one transaction. Posts already publishing or due within 30 seconds block the action. At most 100 affected schedules and 1.5 MB of confirmation content are processed per disconnect. For larger selections, use bulk destination edits in smaller batches first. If confirmation fails, inspect the current channel and schedules before trying again; do not blindly replay a disconnect.
What happens at fire time
At the scheduled moment the post moves from scheduled to queued while a worker claims the job. It then becomes published, processing while the network confirms an asynchronous upload, partial, or failed. Credits are reserved immediately before sending each destination. Confirmed failures are refunded. Subscribe to webhooks for outcome notifications.
Interrupted publishing and recovery
A queued job has a ten-minute publishing lease. A fast delivery retry waits for that lease instead of abandoning the post. A bounded background check also discovers expired scheduled claims. Recovery preserves saved destination receipts and resumes only the remaining scheduled destinations when they have no outstanding publishing charge or provider receipt. A previously confirmed destination is never sent again. An uncertain charged destination blocks automatic recovery until its outcome is reconciled. Claim checks also fence late workers from charging or overwriting a recovered job. Reading or refreshing a queued post does not interrupt the publishing worker or roll up its incomplete results.
If a prior charge exists but the provider receipt was not saved, the outcome is uncertain. PostLake does not automatically publish another copy or assume a refund is due. Read the original post with GET /v1/posts/{id} and contact support to reconcile the provider outcome. Do not repeat creation to recover an uncertain publication. Overdue jobs appear in dashboard attention notices, Post Detail and Admin Health.