---
title: "Update posts"
method: PATCH
path: "/api/businesses/{business_id}/posts"
tags: ["posts"]
---

# Update posts

`PATCH /api/businesses/{business_id}/posts`

## Request

- Base URL: `https://api.mymarky.ai`
- URL: `https://api.mymarky.ai/api/businesses/{business_id}/posts`
- Auth: HTTP bearer

Update many posts in one call: explicit edits, or one text replace across matching posts.

Two shapes:
  - `items`: a list of update_post edits, each with its post_id. Applied now,
    one result per item.
  - `match` + `set.caption_replace`: "change X to Y in every post that has
    it". The first call is a dry run: it writes nothing and returns how many
    posts would change, up to 5 before/after samples, the posts it will skip
    and a `confirm_token`. Then send the SAME call with dry_run=false and that
    confirm_token to apply. Only the matched text changes; status and schedule
    never do, and published posts are never touched.

Errors:
    400 PREVIEW_REQUIRED: A rule apply (dry_run=false) came without a confirm_token.
    400 invalid_request: The rule would change more than 200 posts; narrow match.q or match.statuses.
    401 authentication_required: API key or token is missing, invalid, or revoked.
    403 permission_denied: Your plan does not include API access, or this business is not in your org.
    404 resource_not_found: No business with that ID exists.
    409 PREVIEW_STALE: Posts changed since the dry run (or it was already applied). Nothing was written. Run the dry run again and apply with its new confirm_token.
    422 validation_error: Both or neither of the two shapes, find shorter than 4 characters, or more than 50 items.
    429 rate_limit_exceeded: You sent too many requests this minute.

## Path parameters

- `business_id` string, required: A UUID identifier.

## Headers

- `Marky-Version` '2026-04-08' | '2026-07-17' | '2026-08-02' | '2026-08-04' | '2026-08-06'

## Request body

- UpdatePostsRequest
  - `items` PostUpdateItem[], nullable: Explicit edits, one per post, each like an update_post body plus post_id. Applied directly. Use this when you already know each post's new values.
    - `caption` string, nullable: Updated caption text. Note that a channel listed in platform_overrides with its own caption publishes THAT caption, not this one. To change what every channel publishes, either send platform_overrides in the same call with the new per-channel captions, or send platform_overrides: [] to drop the overrides so every channel falls back to this caption. Read the post first to see which channels have one.
    - `status` string, nullable: Move this post through your team's review workflow. NEW = notify your team the post is ready for review (it shows up on the app's review page). DRAFT = a reviewer approved it. REJECTED = a reviewer declined it. These are your team's workflow state — Marky does not gate publishing on them. Only allowed on posts currently in NEW, DRAFT, or REJECTED (use the schedule endpoint to schedule; published posts are immutable).
    - `restrict_publish_to` string[], nullable: Restrict publishing to these platforms (e.g. instagram, facebook, linkedIn). Case-insensitive. Omit or set null to publish to ALL connected platforms.
    - `media_ids` string[], nullable: Replace the attached media, by library id. Marky media ids to attach, in order. Prefer this over media_urls whenever the media is already in Marky — an image you just generated (use its `media_id`), an upload, or a library search result. Ids are verified against your workspace, so a mistyped one fails clearly instead of looking like a broken link. Cannot be combined with media_urls or design_id. SEVERAL ids on ONE post IS a carousel: they publish as a single multi-image post in the order you list them (Instagram assembles them into a carousel, LinkedIn into a swipeable document). There is no separate carousel endpoint and you do not need one — never split the slides across several posts.
    - `allow_media_reuse` boolean: Leave false. Set true only when the user asked to post a specific photo again (a throwback, a signature shot). Without it, a library photo another recent post already uses is refused with 409 MEDIA_RECENTLY_USED; search the library again and pick a photo you have not used.
    - `media_urls` string[], nullable: Replace the attached media. URLs to images or videos. Use this to swap the image on a generated post during review. Prefer media_ids when the media is already in Marky. Do not set this when design_id is given — media comes from the design automatically.
    - `design_id` string, nullable: Attach a design (from create_design) to this post. When set, media is taken from the design automatically — do NOT also pass media_urls. Editing the design later keeps this post's media in sync.
    - `cover_media_id` string, nullable: Replace this video's cover image. Marky media id of an IMAGE to use as this video's cover (the thumbnail shown before it plays). Upload the still with `POST /api/media/from-url` or `POST /api/media/uploads` first and pass the id it returns. Used by Instagram (Reel cover), Facebook and Pinterest. TikTok can only take a frame of the video itself, so it uses `video_cover_timestamp_ms` instead; LinkedIn, X, YouTube and Google Business pick their own. Ignored on a post with no video. Replacing the post's media later does NOT clear the cover — send a new one.
    - `video_cover_timestamp_ms` integer, nullable: Which frame of the video TikTok should use as its cover, in milliseconds from the start (TikTok's API takes a timestamp, never an image). Defaults to 1000 (one second in). Other platforms ignore this and use `cover_media_id` when you send one.
    - `link` string, nullable: Clickable destination URL for platforms that support link posts: the Facebook link attachment, the Google Business CTA button, and the Pinterest pin destination. Other platforms ignore it — put links in the caption there. Must be http(s).
    - `metadata` object, nullable: Up to 50 string key/value pairs for your own analytics dimensions (e.g. {'media_type': 'animated-video', 'format': 'talking-head', 'style': 'data-take', 'hook': 'question', 'topic': 'automate-with-ai'}). Keys <=40 chars, values <=500 chars. Returned verbatim on every read; Marky never interprets them.
    - `platform_overrides` PlatformOverrideModel[], nullable: REPLACES the post's entire override set: send the full list you want to keep, send [] to clear all overrides, or omit/null to leave them unchanged.
      - `platform` string, required: Platform this override applies to (e.g. instagram, linkedIn, twitter). Case-insensitive.
      - `caption` string, nullable: Caption this platform gets instead of the post's caption. Tailor length, hashtag count, and link placement to the platform.
      - `media_urls` string[], nullable: Media this platform gets instead of the post's media_urls.
      - `title` string, nullable: Title, on platforms that have one (Pinterest pin title, YouTube video title).
      - `link` string, nullable: Destination link, on platforms that support one (e.g. Pinterest, Google Business).
      - `first_comment` string, nullable: Comment auto-posted right after publishing on this platform — the usual home for hashtags and links on Instagram/LinkedIn.
    - `restrict_publish_to_integration_ids` string[], nullable: Restrict publishing to these exact connected accounts (integration ids from `GET /businesses/{business_id}/integrations`). Use instead of restrict_publish_to when a business has multiple accounts of the same platform and you want specific ones. Omit or set null for platform-level targeting. Cannot be combined with restrict_publish_to in one request.
    - `post_id` string, uuid, required: The post to update.
  - `match` RuleMatch
    - `q` string, nullable: Optional keyword search, as in list_posts. Posts must ALSO contain the find text.
    - `statuses` string[]: Which posts to consider. Default: every unpublished status.
  - `set` RuleSet
    - `caption_replace` CaptionReplace, required
      - `find` string, required: Exact text to find (at least 4 characters). Literal, not a pattern.
      - `replace` string, required: Text to put in its place. May be empty.
      - `case_sensitive` boolean: False (default) also matches ProtectCopiers.COM when find is protectcopiers.com.
  - `dry_run` boolean, nullable: Rule mode only. Default true: returns what would change and a confirm_token, and writes nothing. Send dry_run=false with that confirm_token to apply (sending the confirm_token alone also applies).
  - `confirm_token` string, nullable: Rule mode apply: the confirm_token from the dry run.

## Response `200`

Successful Response

- UpdatePostsResponse
  - `confirm_token` string, nullable: Rule dry run only. Send it back with dry_run=false to apply.
  - `dry_run` boolean, required
  - `matched` integer, nullable: Rule mode: unpublished posts that contain the find text.
  - `will_change` integer, nullable: Rule mode: how many of those the apply will change.
  - `changed` integer, nullable: Posts actually written by this call.
  - `skipped` SkippedPost[]
    - `id` string, required
    - `reason` 'PUBLISHED' | 'PUBLISHING' | 'CAPTION_TOO_LONG', required: PUBLISHED: already live, never edited. PUBLISHING: going out right now. CAPTION_TOO_LONG: the replace would push a caption over its platform's limit; fix that post by hand.
  - `samples` ReplaceSample[]
    - `id` string, required
    - `before` string, required: The text around the first match, as it is now.
    - `after` string, required: The same text after the replace.
  - `results` ItemResult[]
    - `index` integer, nullable: Position of the item in your request (list calls only).
    - `post_id` string, nullable: The post this result is about.
    - `ok` boolean, required
    - `revision_id` string, nullable: Pass to revert_revisions to undo.
    - `post` PostBrief: A compact view of one written post. 50 full posts would swamp an agent's context.
      - `id` string, required
      - `status` string, nullable, required
      - `caption_preview` string, nullable, required: First 140 characters.
      - `media_ids` string[], required: Library ids of the attached media, in order.
      - `scheduled_publish_time` string, nullable
      - `scheduled_publish_time_local` string, nullable
      - `revision_id` string, nullable: Pass to revert_revisions to undo.
      - `warnings` FieldWarning[]
        - `field` string, required: The field this warning is about, e.g. 'strategy', 'caption' or 'platform_overrides[0].caption'.
        - `code` string, nullable: Stable machine-readable code, e.g. 'cta_without_contact'. Branch on this, never on the message. Null on the older size-budget warnings.
        - `level` 'approaching_limit' | 'over_limit' | 'advisory', required: 'approaching_limit' = past the soft limit, act soon. 'over_limit' = past the hard limit, act now. 'advisory' = worth fixing before this publishes, but it may be intended.
        - `message` string, required: Plain-language explanation of what is wrong and how to fix it.
    - `error` ItemError
      - `status_code` integer, required: The HTTP status the singular call would have returned.
      - `code` string, nullable: Stable error code, when the error has one.
      - `message` string, required: What went wrong with this item, in plain words.

## Other responses

- `400`: Bad request — the input was malformed or otherwise invalid.
- `401`: Authentication failed — the API key is missing or invalid.
- `403`: Forbidden — the key is valid but may not access this resource.
- `404`: Not found — no resource matches the id in the path.
- `422`: Validation error — one or more fields failed validation.
- `429`: Rate limited — retry after the window resets (see Retry-After).
- `500`: Server error — something failed on our side; retry or quote X-Request-Id.

---

[API](https://skmtc.dev/mymarky/apis/marky-public-api.md) · [All operations](https://skmtc.dev/mymarky/apis/marky-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mymarky/apis/marky-public-api/revisions/25aeacae455c?raw)
