Change a post that hasn't gone out

Caption, attachments, time, per-platform options — anything omitted is kept. It takes the composer's own edit path: a new post supersedes the old one, so guardrails, caps and the signature all run again on the words that will actually be published (and the response carries the NEW id). Any post in the project that hasn't gone out — pending_approval / draft / scheduled / rejected, whoever wrote it. An agent's edit of a post a human approved (or rejected) goes back into the queue, because the approval was for the old words. A post that has already been tried can't be edited: retry the delivery that failed instead.

patch/posts/{id}

Request

  • Base URL: https://xpost.to/api/v1
  • URL: https://xpost.to/api/v1/posts/{id}
  • Auth: HTTP bearer

Path parameters

idstring uuid required

Request body

captionstring
media_idsstring[]
upload_ticketstring

A ticket from POST /media/ticket that files have been posted to: they become the attachments, in order, after anything in media_ids.

add_media_idsstring[]

Appended after what the post already holds (or after media_ids when both are sent), duplicates dropped — an edit that adds a picture without knowing the current set.

scheduled_atstring nullable

A new instant (Z or offset); next_slot hands the post to the project's queue; null clears the schedule

platform_configurationsobject

Per-platform options, keyed by platform name (x, instagram, facebook, threads, pinterest, tiktok, youtube, linkedin, bluesky). Common keys: caption (override the main caption for that platform), placement ("reels" | "stories" | "timeline" — instagram/facebook; threads supports reels only; reels need a video, stories need media and print no caption). Pinterest: title, board_ids, link. X: first_comment (auto-replied under the tweet), thread (up to 4 follow-up tweet texts chained under the main tweet), reply_settings (X polls are not supported — a poll is refused). Threads poll options are at most 25 characters each; a Threads topic tag at most 50, with no . or &. Threads: topic_tag (one word, no #), reply_control, poll_option_a…poll_option_d (at least two; a poll only rides a post with NO media, and this API is the only way to set one — the dashboard composer has no poll fields), gif_id (a giphy.com link, same media-free rule), crosspost_ig_story. Platform disclosure labels, set at publish and impossible to add afterwards: Instagram is_paid_partnership + branded_content_sponsors (max 2 usernames) + is_ai_generated; TikTok is_brand_content (a third party paid) / is_organic_brand_content (your own business) / is_ai_generated; YouTube has_paid_product_placement + contains_synthetic_media; Pinterest is_ai_generated. Instagram: first_comment too (max 2200 characters, not on stories) — but only for accounts connected through bundle.social, since Instagram refuses the comment on the other route; asking for one anywhere else is refused at create time, naming the account. Every text field here is guardrail-checked exactly like the caption — including keys not listed above, since anything that reaches a platform is checked. Machine values (placement, board_id, reply_settings, privacy_status and similar enums and ids) are exempt: they are not places a message can hide.

Example request

{
  "platform_configurations": {
    "instagram": {
      "placement": "stories"
    },
    "x": {
      "first_comment": "Link in this comment 👇"
    }
  }
}

Response

Edited — note the new id

idstring uuid
statusstring
replacedstring uuid
scheduled_atstring date-time nullable

The slot the edited post holds — inherited from the original when you named no time, and null when there is no slot at all (see publishes)

publishes'at_time' | 'on_approval' | 'now' nullable

What has to happen before the edited post goes out: a chosen time, a person's approval, nothing at all (now), or null when it is parked as a draft.

timezonestring

The project's IANA zone, the clock scheduled_at is on

approval_urlstring

Changes

No recorded changes to this operation across all 1 revision of this API.