update_draft
Update a post's content, media, platforms and/or tags. Works on draft, scheduled, and pending-review posts (tags alone can be changed on any post). The existing schedule is preserved. Call schedule_post or queue_post to change when a post fires. Posts that are already publishing or posted cannot be edited. Content is saved verbatim with no AI pass: if you are rewriting the copy yourself, call get_brand_voice first so the edit stays in the user's voice and on-brand. If the post has per-channel copies, changing content changes the shared draft only: every channel that carries its own copy keeps it exactly as it is. To give one channel different text, or change a channel's own copy, pass channel_copies; don't make a second post. Pass first_comment to add, change or remove the comment posted under the post once it is live (LinkedIn, Facebook, Instagram), or first_comments to set several, each from its own profile. Pass stage to move the post to another stage of the editorial workflow (the Content board's columns); it can be set on its own, on any post, and follows the app's rules: only legal moves, one step at a time, and holding a post needs a stage_note. Pass author to give the post to someone else on the workspace, or platform_account_ids to change which connected account a channel publishes from (not once the post is out). Pass penciled_at to pencil a draft in at a date and time, move it, or null to clear it. Drafts never post. A penciled time only holds the slot. The words an edit replaces are kept in the post's version history, where the user can restore them in DemandBird.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Post ID to update | |
| tags | No | Replaces the post's tags; pass [] to remove them all. Omit to leave them as they are. Works on any post, including one already published. For a post imported from the platform, use tag_published_post. Tag names for the post, as they appear on the Content page (e.g. ["Launch", "Case study"]). Matched to the workspace's existing tags ignoring case; a name that doesn't exist yet is created if the user may manage tags, otherwise nothing is saved and the reply lists the tags that exist. | |
| stage | No | Move the post to this editorial stage. Legal moves only: needs_writing → drafting; drafting → internal_review or client_review; internal_review → client_review or revision_requested; client_review → revision_requested or ready_to_schedule; ready_to_schedule → done. Any open stage can go to on_hold or rejected, and most can go back to drafting. A refused move says which moves are legal. Moving to client_review invites the post's pending client reviewers; on_hold takes it off the calendar. | |
| author | No | Optional: who the post belongs to. A teammate's name, or 'me'. Omit to leave it as it is. Only people who can see everyone's posts may name someone else, and that person must be able to see this workspace. | |
| images | No | Images, a video, or one PDF to attach (replaces existing media). For ANY image beyond a thumbnail, call upload_image FIRST to get a presigned S3 URL, upload via the returned curl command, then pass the s3_key here. Inline `data` is only for tiny images (≤50 KB); `file_path` is dev-only. A PDF posts as a LinkedIn document (a swipeable carousel): LinkedIn only, one per post, never alongside images or video. | |
| content | No | New post content (replaces existing) | |
| youtube | No | YouTube-specific options. Required platform inputs: a video must be attached via `images` (the upload tool accepts video MIME types). If `title` is omitted, it is derived from the first line of content (truncated to 100 chars). | |
| platforms | No | Updated target platforms. Instagram needs an image or video on the post: upload one with `upload_image` (or make one with `generate_image`) and pass its s3_key in `images`. | |
| stage_note | No | Why the post is moving. Required for on_hold, so the writer knows what to fix. | |
| penciled_at | No | Optional: pencil the draft in at a date and time, ISO 8601 (e.g. '2026-10-20T09:00'; no offset means the workspace's posting zone). Drafts never post. A penciled time only holds the slot. It stays a draft: nothing is scheduled or queued, and the queue skips that slot. A date with no time is refused. schedule_post with no `scheduled_at` books it at that time. Pass null to clear it. | |
| first_comment | No | Optional: a comment DemandBird posts under the post, from the same profile, once the post is live (often a link or a call to action kept out of the post itself). LinkedIn, Facebook and Instagram only here. Stored verbatim. Pass {"text": ""} to remove it. Omit to leave it as it is. | |
| channel_copies | No | Optional: a channel's own text, keyed by platform, e.g. {"twitter": "..."} for an X version of a LinkedIn post. That text publishes on that channel instead of the shared `content`; every other channel keeps what it had. Stored verbatim. A channel named here is added to the post's platforms. Pass "" for a channel to remove its copy (it then publishes the shared content). Use this, never a second post, to give one channel different text. | |
| first_comments | No | Optional: up to 5 comments DemandBird posts under the post once it is live, in order, each on its own delay and, if `comment_as` is set, from its own profile or page (a teammate adding their thoughts). Replaces every first comment on the platforms it covers; [] removes them all. Stored verbatim. Use this or `first_comment`, not both. Omit to leave the comments as they are. | |
| platform_account_ids | No | Optional map of platform => connected_account_id (numeric id from `list_connected_accounts`): which account each platform publishes from. Example: {"linkedin": 42}. Each platform must already be one the post publishes on, and each id a channel of this workspace that matches the platform, or nothing is saved. On a scheduled post the schedule moves to the new account too. Refused once the post is published. |