---
title: "Create posts"
method: POST
path: "/api/businesses/{business_id}/posts/batch"
tags: ["posts"]
---

# Create posts

`POST /api/businesses/{business_id}/posts/batch`

## Request

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

Create several posts in one call, each exactly as create_post would.

Use this instead of many create_post calls: "a post a day for a month" is
one call. Each item succeeds or fails on its own, so read `results`: an
item that failed says why in `error`, and nothing was saved for it.

Errors:
    400 invalid_value: The business_id is not a valid UUID.
    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.
    422 validation_error: No posts, more than 50, or an item is not a valid create_post body.
    429 rate_limit_exceeded: You sent too many requests this minute.

## Path parameters

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

## Headers

- `Idempotency-Key` string, nullable: Optional. Send a unique value (e.g. a UUID) to make this create request safe to retry. A repeat with the same key returns the original response instead of creating a duplicate. Cached for 24h, scoped to your org. Reusing a key with a different body returns 422.
- `Marky-Version` '2026-04-08' | '2026-07-17' | '2026-08-02' | '2026-08-04' | '2026-08-06'

## Request body

- CreatePostsRequest
  - `posts` PostCreateRequest[], required: The posts to create, 1-50, each exactly like a create_post body. ONE item per post: a carousel is one item with several media_ids.
    - `caption` string, required: Post caption text. This is the caption every channel publishes unless it has its own in platform_overrides.
    - `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: 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.
    - `media_urls` string[], nullable: URLs to images or videos to attach. Use this only for media that is NOT already in Marky — otherwise pass media_ids, which cannot be mistyped. Foreign URLs are copied into Marky storage at save time, so the link is fetched now and must be public and exact. Do not set this when design_id or media_ids is given.
    - `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: 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.
    - `status` string: Initial post status
    - `scheduled_publish_time` string, date-time, nullable: The exact time to publish (ISO 8601). Required if status is SCHEDULED; must be in the future. Send an offset or `Z` (e.g. `2026-09-23T09:10:00Z`) for an exact instant. A time with NO offset (e.g. `2026-09-23T09:10:00`) is read as the business's local time in its calendar timezone (UTC if it has none), daylight saving included.
    - `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: Per-platform variants of this post — one entry per platform you want to tailor. Unset fields fall back to the post's own values. Strongly recommended whenever a post targets more than one platform: a caption that fits Instagram is usually wrong for X/Twitter (280 chars) and LinkedIn (no hashtag walls).
      - `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.
    - `append_caption_suffix` boolean: Set true to have Marky append the business's caption suffix (its hashtag block / sign-off) to the caption. Never type the suffix yourself; with this set it appears exactly once, and hashtags it already carries are removed from the body.
    - `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.
    - `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.
  - `idempotency_key` string, nullable: Makes a retry safe: send the same key with the same posts and you get the original results back instead of duplicates. Item i is stored under '<key>:<i>'. Same as the Idempotency-Key header; the header wins if both are sent.

## Response `200`

Successful Response

- CreatePostsResponse
  - `created` integer, required: How many items were saved.
  - `results` ItemResult[], required: One entry per item, in the order you sent them.
    - `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)
