---
title: "Create and publish a post immediately"
method: POST
path: "/posts/create-and-publish"
tags: ["Posts"]
---

# Create and publish a post immediately

`POST /posts/create-and-publish`

Creates a new post and publishes it right away — no scheduling needed. Equivalent to calling POST /posts/create with publish_now: true. The post will be queued for immediate publishing to all selected platforms.

## Request body

- CreatePostRequest
  - `content` object, required — Post content. Use 'default' for all platforms, or platform-specific keys: instagram, facebook, linkedin, linkedin_page, youtube, tiktok, pinterest, x, threads, bluesky, mastodon, google_business. Coming soon: reddit, snapchat.
  - `accounts` string[], required — Social accounts to post to. Each entry is either: - A bare platform name (e.g. `"youtube"`, `"linkedin_page"`), or - The composite ID returned by `GET /v1/accounts` in the form `"<workspace_id>_<platform>"` (e.g. `"844008_youtube"`). The composite form's workspace prefix must match the workspace your API key belongs to. Mismatched or unknown ids return 400 `validation_error` with an "Unknown account/platform" message — always source ids from `GET /v1/accounts` for the API key in use.
  - `type` 'post' | 'story' | 'reel' — Post type: 'post' (feed), 'story' (Instagram/Facebook), 'reel' (Instagram/Facebook/YouTube/TikTok). A 'reel' must have video media (MP4/MOV); an image is rejected with 400 validation_error at schedule/publish — use 'post' to share an image. A 'story' takes 1 to 10 media items: each item is one slide, published as its own story in the order given (Instagram and Facebook have no multi-slide story container). More than 10 returns 400 validation_error.
  - `media` union — Media IDs from POST /media/upload. Flat array or per-platform object. Any entry may be an { id, alt } object to carry a per-media accessibility description (alt text, max 1500 chars). For type 'story', each item is one slide (max 10, published in order).
    - union[] — Flat array of media IDs — same for all platforms. Entries are bare IDs or { id, alt } objects.
      - union
        - string
        - object
          - `id` string, required
          - `alt` string — Accessibility description (alt text) for this media item. Delivered to Mastodon (media description), Bluesky (embed alt), X (media metadata, photos/GIFs only), Pinterest (pin alt_text fallback), Instagram (images and carousel image slides — not Reels/Stories) and LinkedIn (images only — not video or documents).
    - object — Per-platform object — keys: 'default', 'instagram', 'pinterest', etc. Entries are bare IDs or { id, alt } objects.
  - `media_urls` union — External image/video/PDF URLs to download and attach (max 10 total, each file ≤ 100 MB). Flat array or per-platform object. Any entry may be a { url, alt } object to carry a per-media accessibility description (alt text, max 1500 chars) — delivered to Mastodon, Bluesky, X, Pinterest, Instagram (images) and LinkedIn (images). When using per-platform format, 'default' acts as fallback for selected platforms without their own key. A PDF URL is rasterized into one image slide per page (max 20, in order) — on LinkedIn it publishes as a swipeable document, elsewhere as an image carousel; slides inherit the entry's alt. For files over 100 MB (up to 1 GB), upload first via POST /v1/media/upload-from-url and attach the returned media id via the `media` field; oversized media_urls fail with code `file_too_large`.
    - union[] — Flat array — same media for all platforms. Entries are URL strings or { url, alt } objects.
      - union
        - string, uri
        - object
          - `url` string, uri, required
          - `alt` string — Accessibility description (alt text) for this media item. Delivered to Mastodon (media description), Bluesky (embed alt), X (media metadata, photos/GIFs only), Pinterest (pin alt_text fallback), Instagram (images and carousel image slides — not Reels/Stories) and LinkedIn (images only — not video or documents).
    - object — Per-platform object — keys: 'default' (fallback for all platforms), 'instagram', 'pinterest', etc. Selected platforms without an explicit key inherit from 'default'. Pass an empty array (e.g. 'facebook': []) to explicitly opt a platform out of media. Entries are URL strings or { url, alt } objects.
  - `schedule_at` string, date-time — ISO 8601 datetime to schedule. Omit for draft.
  - `publish_now` boolean — Set true to publish immediately
  - `link_url` string, uri — URL to share as a rich preview card on platforms that support link-share posts (currently LinkedIn and Facebook). When set on a text post, the URL renders as a preview tile with thumbnail / title / description instead of plain text. Ignored on platforms that don't support link shares (Instagram, TikTok, etc.) and on posts that already have media attached — media takes precedence.
  - `link_title` string — Optional title for the link-share preview. LinkedIn uses this directly when set. Facebook ignores this — it fetches OG metadata server-side. Omit to let LinkedIn auto-fetch the page title.
  - `link_description` string — Optional description for the link-share preview. LinkedIn uses this when set; Facebook auto-fetches the OG description. Omit to defer to OG metadata.
  - `link_thumbnail_url` string, uri — Optional thumbnail image URL for the preview card. Currently not yet applied to LinkedIn (would require uploading to LinkedIn's image API first); included for forward compatibility.
  - `source` string — Self-reported origin of the integration creating this post. Defaults to 'api' if omitted. Use 'zapier', 'make', 'n8n', etc. to identify your integration.
  - `location_id` string — Instagram only. Facebook Place/Page ID to tag the post's location with (e.g. a dealership or storefront). Applied to single-image and carousel feed posts via Instagram's content-publishing `location_id`. Find a Place's Page ID via Facebook's Pages Search API. Ignored by other platforms. Send an empty string on PATCH to clear it.
  - `collaborators` string[] — Instagram only. Up to 3 public Instagram usernames to invite as co-authors (the 'Collab' feature). Applied to image, carousel, and reel posts — NOT Stories. Invited users get an invitation in the Instagram app; once accepted, the post also appears on their profile and feed. Usernames are case-insensitive and a leading '@' is stripped. Private accounts and non-existent usernames are rejected by Instagram at publish time. Ignored by other platforms. Send an empty array on PATCH to clear collaborators.
  - `user_tags` object[] — Instagram only. Tag public Instagram accounts at specific positions on a photo. Photos only — NOT video, reels, or stories. For a single image omit `image_index` (or set 0); for a carousel, set `image_index` to the 0-based slide the tag belongs to (tags attach to that slide's image). Public accounts only; private/non-existent usernames are rejected by Instagram at publish time. Ignored by other platforms. Send an empty array on PATCH to clear tags.
    - `username` string, required — Public Instagram username to tag. A leading '@' is stripped.
    - `x` number, required — Horizontal position of the tag, 0.0–1.0 from the photo's left edge.
    - `y` number, required — Vertical position of the tag, 0.0–1.0 from the photo's top edge.
    - `image_index` integer — 0-based carousel slide the tag applies to. Defaults to 0 (the only image for a single-image post).
  - `hashtag_set` string — Name of a saved hashtag set (see GET /hashtag-sets) to apply to this post, matched case-insensitively. The set's tags are merged in ONCE at create time — the post stores plain text, so later edits to the set never change this post. Tags already present in a caption are skipped (case-insensitive), and hard platform caps (Instagram 30) are enforced with a 400 `hashtag_limit_exceeded` before the platform can reject the post. Mutually interchangeable with `hashtag_set_id`.
  - `hashtag_set_id` string — Id of a saved hashtag set to apply. Same behavior as `hashtag_set`; pass one or the other.
  - `hashtag_placement` 'caption_append' | 'first_comment' — Where the set's tags land. `caption_append` (default) appends them to each target platform's caption after a blank line. `first_comment` puts them in the auto first comment on comment-capable platforms (Instagram, Facebook, LinkedIn Profile/Page, YouTube, TikTok with comments enabled) — appended after any `first_comment` you passed explicitly — and falls back to caption_append on platforms without a comment API. Stories always use captions (first comments are never posted on Stories).
  - `hashtag_platforms` string[] — Optional subset of the post's selected platforms to apply the set to (e.g. only ["instagram", "tiktok"]). Defaults to all selected platforms.
  - `pinterest` object — Pinterest-specific options. Attach 2–5 images via `media_urls.pinterest` (or the flat `media_urls`) to publish a single carousel pin; the title, description, link, and alt_text apply to the whole pin. A single image or a single video still publishes as a normal image / video pin. **Carousel constraints:** carousels hard-cap at 5 images — attaching more responds 400 `validation_error` at create/schedule time. Pinterest's v5 API also requires every slide in a carousel pin to share the same aspect ratio (1% tolerance). If you attach mixed-ratio images and try to schedule or publish, the API responds 400 `validation_error` with `mismatched_slides: [n, ...]` listing the 1-indexed slide numbers that need to be re-cropped or removed. Both checks run when `publish_now: true`, when `schedule_at` is set, and when `PATCH` flips a draft to a scheduled status — drafts are exempt so you can iterate on media.
    - `board_id` string — Required. Pinterest board ID
    - `title` string — Pin title (max 100 characters). For carousel pins, applies to the whole pin, not individual slides.
    - `link` string — Destination URL for the pin
    - `video_cover` string — Cover image URL for video pins (image URL — JPEG/PNG)
    - `alt_text` string — Accessibility alt text for the pin image (max 500 characters)
  - `youtube` object — YouTube Shorts options. Only applies when `type` is `reel` and `youtube` is among the selected accounts.
    - `title` string — Short title shown on YouTube. Falls back to "YouTube Short" when omitted.
    - `tags` string[] — Video tags
    - `privacy_status` 'public' | 'private' | 'unlisted'
    - `category_id` string — YouTube category ID (default "22" People & Blogs)
    - `made_for_kids` boolean
    - `notify_subscribers` boolean
    - `contains_synthetic_media` boolean — Mark as AI-generated content
    - `first_comment` string — Text auto-posted as the first comment on the video right after it publishes (commentThreads.insert). The video must have comments enabled. Empty/omitted = no first comment.
  - `instagram` object — Instagram Reel options
    - `share_to_feed` boolean — Also share reel to feed
    - `thumbnail_type` 'from-video' | 'from-library' — How to select thumbnail
    - `thumb_offset` integer — Video timestamp in milliseconds (when thumbnail_type is from-video)
    - `cover_url` string — Custom thumbnail URL (when thumbnail_type is from-library)
    - `audio_id` string — Licensed music for the Reel — an audio ID from `GET /audio/search`. Replaces/mixes with the video's own audio at publish time. Requires a Facebook account connected to the workspace whose Page is linked to this Instagram account. Reels only.
    - `audio_volume` integer — Volume of the attached music track (0 = muted). Only used with audio_id.
    - `video_volume` integer — Volume of the video's original audio (0 = muted, e.g. music-only Reel). Only used with audio_id.
    - `first_comment` string — Text auto-posted as the first comment on the post/reel right after it publishes. Common for keeping hashtags out of the caption. Not posted for Stories. Empty/omitted = no first comment.
    - `is_trial_reel` boolean — Publish the reel as an Instagram Trial Reel: shown to non-followers first to test performance before (optionally) graduating to everyone. Reels only. NOT available on every account — Instagram requires roughly 1,000+ followers and enables the feature per account (the account sees a 'Trial' toggle when creating a reel in the Instagram app). Ineligible accounts fail at publish time with a clear per-platform error.
    - `trial_graduation_strategy` 'MANUAL' | 'SS_PERFORMANCE' — How a Trial Reel graduates to all followers. MANUAL: you decide in the Instagram app. SS_PERFORMANCE: Instagram automatically shares it with followers if it performs well. Only used with is_trial_reel.
    - `is_ai_generated` boolean — Self-disclosure that this post's media is AI-generated. Adds Instagram's 'AI info' label. Applies to single images, videos, and Reels; for a carousel the label applies to the whole post, not individual slides. Cannot be added or removed after publish — must be set at post time. Not available for Stories.
  - `facebook` object — Facebook options
    - `first_comment` string — Text auto-posted as the first comment on the post/reel right after it publishes. Only works on Page posts (the API cannot comment on personal-profile posts). Not posted for Stories. Empty/omitted = no first comment.
  - `linkedin` object — LinkedIn Profile options
    - `first_comment` string — Text auto-posted as the first comment on the profile post right after it publishes. Common for 'link in first comment' to avoid the in-caption link reach penalty. Empty/omitted = no first comment.
    - `carousel_as_images` boolean — Multi-image style for the profile post. By default a post with 2+ images publishes as LinkedIn's swipeable PDF document carousel. Set true to publish the images as a plain multi-image gallery instead. Ignored for 0-1 images, video posts, and polls.
  - `linkedin_page` object — LinkedIn Company Page options
    - `first_comment` string — Text auto-posted as the first comment on the company-page post right after it publishes. Empty/omitted = no first comment.
    - `carousel_as_images` boolean — Multi-image style for the company-page post. By default a post with 2+ images publishes as LinkedIn's swipeable PDF document carousel. Set true to publish the images as a plain multi-image gallery instead. Ignored for 0-1 images, video posts, and polls.
  - `linkedin_poll` object, nullable — Non-sponsored LinkedIn poll(s) — independent per channel, keyed by `linkedin` (personal profile) / `linkedin_page` (company page). A poll is mutually exclusive with media and a link share on that channel's post — a poll takes priority over both at publish time. Requires `content.linkedin` (or `content.default`) as that channel's caption/commentary; the poll itself only carries the question/options/duration.
    - `linkedin` LinkedInPollFields
      - `question` string, required — The poll question.
      - `options` string[], required — 2-4 answer options.
      - `duration` 'ONE_DAY' | 'THREE_DAYS' | 'SEVEN_DAYS' | 'FOURTEEN_DAYS', required — How long the poll stays open for votes.
    - `linkedin_page` LinkedInPollFields
      - `question` string, required — The poll question.
      - `options` string[], required — 2-4 answer options.
      - `duration` 'ONE_DAY' | 'THREE_DAYS' | 'SEVEN_DAYS' | 'FOURTEEN_DAYS', required — How long the poll stays open for votes.
  - `tiktok` object — TikTok options
    - `title` string — Photo carousels only. The title TikTok shows above the caption on Photo Mode posts (max 90 characters). TikTok's photo endpoint takes the title and the caption as two separate fields; the caption comes from `content`. Ignored on video posts, which have a single caption field. Longer than 90 characters returns 400 validation_error. Read back as `tiktok.title` on GET /posts/{id}.
    - `privacy_level` 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY'
    - `disable_comment` boolean
    - `disable_duet` boolean
    - `disable_stitch` boolean
    - `video_cover_timestamp_ms` integer — Reels only. Timestamp (ms) of the video frame to use as the cover.
    - `is_aigc` boolean — Mark as AI-generated content
    - `brand_content_toggle` boolean — Paid partnership disclosure (promotes a third-party brand)
    - `brand_organic_toggle` boolean — Your own brand disclosure (promotes your own business)
    - `auto_add_music` boolean — Photo carousels only. When true, TikTok auto-selects a soundtrack. Defaults to false to avoid unsuitable tracks.
    - `first_comment` string — Text auto-posted as the first comment on the TikTok video right after it publishes (max 150 characters). Requires the workspace's TikTok comments authorization (the "Enable comments" button on the TikTok channel card, a second TikTok consent); without it `first_comment_result.status` is `failed` with an explanatory error. The video must be public with comments allowed. TikTok sometimes returns the final video id a few minutes after publish — in that case the comment is posted automatically as soon as the id resolves (`first_comment_result.pending` is true meanwhile). Empty/omitted = no first comment.
  - `x` object — X (Twitter) options
    - `reply_settings` '' | 'following' | 'mentionedUsers' — Who can reply (empty = everyone)
    - `paid_partnership` boolean — Mark as a paid partnership disclosure
    - `made_with_ai` boolean — Mark as AI-generated content
    - `thread_parts` object[] — Publish as a chained thread instead of a single tweet. When omitted (or length < 2), `content.x` (or `content.default`) is used as a single tweet. Each part is posted in order via `in_reply_to_tweet_id`. Attach media to any part (first tweet or any reply) with either `media_ids` (numeric Library IDs from POST /v1/media — the same IDs single posts use) or `media_urls` (external links). Combined max is 4 media items per part. If the post also has top-level `media_urls.x` / media and the first part has no media of its own, the top-level media is attached to the first tweet.
      - `text` string, required — Tweet text. Limit depends on the connected account's tier: 280 chars standard, 25,000 for X Premium/Premium+. Counted the way X counts: every link weighs 23 characters (its t.co length) regardless of the real URL length, and emoji/CJK/most symbols weigh 2.
      - `media_ids` union[] — Optional per-part media as numeric Library IDs returned by POST /v1/media (bare IDs or { id, alt } objects). Resolved to hosted URLs at create time. Combined with media_urls, max 4 per tweet.
        - union
          - string
          - object
            - `id` string, required
            - `alt` string — Accessibility description (alt text) for this media item.
      - `media_urls` union[] — Optional per-part media as external URLs (downloaded + hosted), as URL strings or { url, alt } objects. Combined with media_ids, max 4 per tweet.
        - union
          - string, uri
          - object
            - `url` string, uri, required
            - `alt` string — Accessibility description (alt text) for this media item.
  - `bluesky` object — Bluesky options
    - `thread_parts` object[] — Publish as a chained Bluesky thread instead of a single post. When omitted (or length < 2), `content.bluesky` (or `content.default`) is used as a single post. Each part is posted in order via AT Protocol reply refs (root + parent) so it renders as a thread. Attach media to any part with either `media_ids` (numeric Library IDs from POST /v1/media — the same IDs single posts use) or `media_urls` (external links). A part is a single video OR up to 4 images. If the post also has top-level `media_urls.bluesky` / media and the first part has no media of its own, the top-level media is attached to the first post. Links, mentions and hashtags are made clickable automatically (rich-text facets).
      - `text` string, required — Post text (≤ 300 characters, counted as graphemes — one emoji counts as 1).
      - `media_ids` union[] — Optional per-part media as numeric Library IDs returned by POST /v1/media (bare IDs or { id, alt } objects). Resolved to hosted URLs at create time. A part is one video OR up to 4 images.
        - union
          - string
          - object
            - `id` string, required
            - `alt` string — Accessibility description (alt text) for this media item.
      - `media_urls` union[] — Optional per-part media as external URLs (downloaded + hosted), as URL strings or { url, alt } objects. A part is one video OR up to 4 images.
        - union
          - string, uri
          - object
            - `url` string, uri, required
            - `alt` string — Accessibility description (alt text) for this media item.
  - `mastodon` object — Mastodon options
    - `thread_parts` object[] — Publish as a chained Mastodon thread instead of a single status. When omitted (or length < 2), `content.mastodon` (or `content.default`) is used as a single status. Each part is posted in order as a native reply to the previous status (in_reply_to_id) so it renders as a thread. Attach media to any part with either `media_ids` (numeric Library IDs from POST /v1/media — the same IDs single posts use) or `media_urls` (external links) — max 4 media items per part. If the post also has top-level `media_urls.mastodon` / media and the first part has no media of its own, the top-level media is attached to the first status.
      - `text` string, required — Status text (≤ 500 characters by default; some instances allow more).
      - `media_ids` union[] — Optional per-part media as numeric Library IDs returned by POST /v1/media (bare IDs or { id, alt } objects). Resolved to hosted URLs at create time. Max 4 media items per part.
        - union
          - string
          - object
            - `id` string, required
            - `alt` string — Accessibility description (alt text) shown to screen-reader users — the Mastodon community strongly values alt text on images.
      - `media_urls` union[] — Optional per-part media as external URLs (downloaded + hosted), as URL strings or { url, alt } objects. Max 4 media items per part.
        - union
          - string, uri
          - object
            - `url` string, uri, required
            - `alt` string — Accessibility description (alt text) shown to screen-reader users — the Mastodon community strongly values alt text on images.
  - `threads` object — Threads options: thread mode via `thread_parts`, location tag via `location_id`.
    - `location_id` string, nullable — Tag a location on the Threads post. Use an `id` returned by `GET /locations/search?platform=threads&q=...` (Threads location ids are NOT Facebook Place IDs, so do not reuse the Instagram `location_id` here). Sent to Threads as `location_id` on the container create call; on a multi-post thread (`thread_parts`) the tag goes on the first post. Requires the workspace's Threads connection to have the `threads_location_tagging` permission: create/update/publish return 400 `validation_error` naming the reconnect when it is missing, and 400 when location tagging is not enabled in this environment yet. On PATCH, `null` clears the tag. Alternative: pass `location` as an object `{ id, name, address, city, country }` to store display fields along with the id; `location_id` wins when both are given.
    - `location` object, nullable — Alternative to `location_id`: the full search result so the dashboard can show the place name without another Threads call. Only `id` is required; the other fields are stored as given (not resolved against Threads). `null` clears the tag on PATCH.
      - `id` string
      - `name` string
      - `address` string
      - `city` string
      - `country` string
    - `thread_parts` object[] — Publish as a chained Threads thread instead of a single post. When omitted (or length < 2), `content.threads` (or `content.default`) is used as a single post. Each part is posted in order as a native reply to the previous post (reply_to_id) so it renders as a thread under the first post. Attach media to any part with either `media_ids` (numeric Library IDs from POST /v1/media, the same IDs single posts use) or `media_urls` (external links), max 10 media items per part (images and videos can be mixed; 2+ items publish as a carousel). If the post also has top-level `media_urls.threads` / media and the first part has no media of its own, the top-level media is attached to the first post.
      - `text` string, required — Post text (up to 500 characters).
      - `media_ids` union[] — Optional per-part media as numeric Library IDs returned by POST /v1/media (bare IDs or { id, alt } objects). Resolved to hosted URLs at create time. Max 10 media items per part.
        - union
          - string
          - object
            - `id` string, required
            - `alt` string — Accessibility description (alt text) sent to Threads as alt_text.
      - `media_urls` union[] — Optional per-part media as external URLs (downloaded + hosted), as URL strings or { url, alt } objects. Max 10 media items per part.
        - union
          - string, uri
          - object
            - `url` string, uri, required
            - `alt` string — Accessibility description (alt text) sent to Threads as alt_text.
  - `google_business` object — Google Business Profile options. Use to publish EVENT or OFFER posts in addition to STANDARD, attach a call-to-action button, or both. STANDARD posts can also use this object purely to add a CTA. The shape mirrors Google's LocalPost JSONB exactly; see https://developers.google.com/my-business/reference/rest/v4/accounts.locations.localPosts#LocalPost. Caption rules (enforced at scheduling — text that violates these returns `validation_error` 400 before the post is saved): • No phone numbers in the caption (use a CALL button instead). • No inline URLs / bare domains / email addresses (use a LEARN_MORE, BOOK, SHOP, SIGN_UP, or ORDER button instead). • Caption max 1500 characters. • Media optional (text-only posts allowed). If attached: exactly one JPEG/PNG/WebP image (no video, no carousels). • The workspace must have a Google Business location selected (Settings → Organisation → Workspaces → Google Business) before scheduling.
    - `topic_type` 'STANDARD' | 'EVENT' | 'OFFER' — Local post topic type. ALERT is reserved by Google and not exposed.
    - `cta` object — Optional call-to-action button rendered under the caption on the Google Business Profile.
      - `actionType` 'LEARN_MORE' | 'BOOK' | 'ORDER' | 'SHOP' | 'SIGN_UP' | 'CALL'
      - `url` string, uri — Required for every actionType except CALL. CALL uses the location's phone number from the business profile.
    - `event` object — Required when topic_type is EVENT.
      - `title` string
      - `schedule` object — Google's split date+time shape. startDate is required; startTime/endDate/endTime are optional. End (if present) must be ≥ start.
        - `startDate` object
          - `year` integer
          - `month` integer
          - `day` integer
        - `startTime` object
          - `hours` integer
          - `minutes` integer
        - `endDate` object
          - `year` integer
          - `month` integer
          - `day` integer
        - `endTime` object
          - `hours` integer
          - `minutes` integer
    - `offer` object — Required when topic_type is OFFER. Must include at least one of couponCode or redeemOnlineUrl.
      - `couponCode` string
      - `redeemOnlineUrl` string, uri — Must be https://. http URLs are rejected.
      - `termsConditions` string

## Response `201`

Post created and queued for immediate publishing

- object
  - `data` Post
    - `id` string — Post ID
    - `status` 'draft' | 'in_approval' | 'scheduled' | 'posting' | 'published' | 'failed' | 'warning' | 'rejected' — `in_approval` = waiting for a reviewer in an approval workflow; PATCH /posts/{id} can still move its `schedule_at` without changing this status. `rejected` = an approver declined the post via POST /posts/{id}/reject; it will not be published. `posting` = a publish is in flight. `warning` = some platforms published and some failed (see `errors` for the failures, `published_urls` for the successes); retry the failures with POST /posts/{id}/retry. `failed` = every platform failed. NOTE: a failed post whose dashboard 'retry as new post' succeeded flips to `published` even though it has no `published_urls` of its own — check `retries` for the post that actually carries the live URLs.
    - `type` 'post' | 'story' | 'reel' — Post type. 'reel' media must be a video (MP4/MOV) — an image is rejected with 400 validation_error at schedule/publish; use 'post' to share an image. A 'story' carries 1 to 10 media items; every item is one slide and publishes as its own story, in order.
    - `story_slides` object — Stories only, after publishing. Per-platform list of the published slides in publish order. `published_urls` keeps the first slide's URL per platform; this field has every slide.
    - `content` object — Post content per platform
    - `accounts` string[] — Account IDs to post to
    - `media` object — Per-platform media object. Keys: default, instagram, facebook, etc. Each value is an array of {url, id, channel, alt?}.
    - `location_id` string — Instagram location tag (Facebook Place/Page ID), echoed back from the create/update request. Omitted when not set.
    - `collaborators` string[] — Instagram co-author usernames (the 'Collab' feature), echoed back from the create/update request. Omitted when none set.
    - `user_tags` object[] — Instagram photo user tags, echoed back from the create/update request. Omitted when none set.
      - `username` string
      - `x` number
      - `y` number
      - `image_index` integer
    - `schedule_at` string, date-time — Scheduled publish time (ISO 8601)
    - `app_url` string, uri — Deep link to open this post inside the OmniSocials app, correct for the environment (staging vs production). Draft, scheduled, and failed posts open in the composer; published posts open in the details view. Hand this to the user so they can click straight through to review a draft you just created.
    - `published_urls` object — Live URLs of the post on each platform after publishing. Keys are platform identifiers (`facebook`, `instagram`, `linkedin`, `linkedin_page`, `youtube`, `tiktok`, `pinterest`, `x`, `threads`, `bluesky`, `mastodon`, `google_business`). Only platforms that successfully published appear; failed platforms are omitted (see `errors`). Empty object `{}` while the post is still draft, scheduled, or processing.
    - `retry_of` string — Present when this post was created by the dashboard 'retry as new post' flow: the ID of the original failed post it retries. The live platform URLs are on THIS post's `published_urls`.
    - `retries` string[] — Present on a post that has been retried as a new post: the IDs of its retry posts (newest last). A `published` post with empty `published_urls` and `retries` set is a resolved failure — the actual publish lives on the retry post, so don't double-count it.
    - `created_at` string, date-time
    - `approval_status` 'none' | 'pending' | 'approved' | 'rejected', nullable — Approval workflow status. `none` = no approval workflow attached. `pending` = waiting on an approver (post `status` is `in_approval`); act on it with POST /posts/{id}/approve or /reject. `approved` / `rejected` = the workflow finished.
    - `errors` object, nullable — Per-platform user-friendly error messages, keyed by platform identifier (`facebook`, `instagram`, `linkedin`, `linkedin_page`, `youtube`, `tiktok`, `pinterest`, `x`, `threads`, `bluesky`, `mastodon`, `google_business`). Populated when `status` is `failed` or `warning`. Only platforms that failed appear; successful platforms are omitted (see `published_urls`). `null` while the post is still draft/scheduled/processing or every platform succeeded. A platform's entry is removed once a retry of it succeeds (via POST /posts/{id}/retry or the dashboard). Mirrors the per-platform error a user sees in the dashboard with a Retry button.
    - `source` string, nullable — Origin of post creation: web, api, mcp, workflow, zapier, make, n8n, etc.
    - `updated_at` string, date-time
    - `x` object, nullable — X (Twitter) options on the post. Includes per-publish settings (reply_settings, made_with_ai, paid_partnership) plus the canonical thread shape. Present when the post has X-specific data; omitted otherwise.
      - `reply_settings` '' | 'following' | 'mentionedUsers'
      - `paid_partnership` boolean
      - `made_with_ai` boolean
      - `thread_parts` object[] — When the post is a chained X thread (2–25 parts), this returns the canonical tweet text in publish order. For single-tweet posts the array is omitted and the text lives in `content.x` (or `content.default`). Each part also includes any per-tweet `media_urls`.
        - `id` string — Stable client-side id for the part
        - `text` string — 280 chars for standard accounts, 25,000 for X Premium/Premium+.
        - `media_urls` union[]
          - union
            - string, uri
            - object
              - …
    - `bluesky` object, nullable — Bluesky options on the post. Present when the post is a Bluesky thread; omitted otherwise.
      - `thread_parts` object[] — When the post is a chained Bluesky thread (2–25 parts), this returns the canonical post text in publish order. For single posts the array is omitted and the text lives in `content.bluesky` (or `content.default`). Each part also includes any per-post `media_urls`.
        - `id` string — Stable client-side id for the part
        - `text` string
        - `media_urls` union[]
          - union
            - string, uri
            - object
              - …
    - `mastodon` object, nullable — Mastodon options on the post. Present when the post is a Mastodon thread; omitted otherwise.
      - `thread_parts` object[] — When the post is a chained Mastodon thread (2–25 parts), this returns the canonical status text in publish order. For single posts the array is omitted and the text lives in `content.mastodon` (or `content.default`). Each part also includes any per-status `media_urls`.
        - `id` string — Stable client-side id for the part
        - `text` string
        - `media_urls` union[]
          - union
            - string, uri
            - object
              - …
    - `threads` object, nullable — Threads options on the post. Present when the post is a Threads thread (multi-post chain) or carries a location tag; omitted otherwise.
      - `location` object, nullable — Location tagged on the Threads post (sent as `location_id` to Threads at publish; on a multi-post thread it is tagged on the first post). Present only when set. `name`/`address`/`city`/`country` are whatever the caller stored alongside the id (the dashboard stores the search result; the API stores what you pass in `threads.location`).
        - `id` string
        - `name` string, nullable
        - `address` string, nullable
        - `city` string, nullable
        - `country` string, nullable
      - `thread_parts` object[] — When the post is a chained Threads thread (2 to 25 parts), this returns the canonical post text in publish order. For single posts the array is omitted and the text lives in `content.threads` (or `content.default`). Each part also includes any per-post `media_urls`.
        - `id` string — Stable client-side id for the part
        - `text` string
        - `media_urls` union[]
          - union
            - string, uri
            - object
              - …
    - `instagram` object, nullable — Instagram options on the post. Present when set; includes `first_comment` (the configured auto first comment) and, after publish, `first_comment_result` describing the outcome.
      - `first_comment` string — The configured first-comment text.
      - `first_comment_result` FirstCommentResult — Outcome of an auto first-comment attempt, written after the main post publishes.
        - `status` 'posted' | 'failed' | 'skipped' — `posted` = comment is live; `failed` = the main post published but the comment did not (see `error`; retry via POST /posts/retry-first-comment); `skipped` = the main post did not publish so the comment was never attempted.
        - `pending` boolean, nullable — TikTok only. `true` while the comment waits for TikTok to return the final video id (status is `failed` with an explanatory error meanwhile); the comment is posted automatically once the id resolves, usually within minutes.
        - `comment_id` string, nullable
        - `url` string, nullable
        - `error` string, nullable
        - `posted_at` string, date-time, nullable
    - `facebook` object, nullable — Facebook options on the post — `first_comment` and its post-publish `first_comment_result`.
      - `first_comment` string
      - `first_comment_result` FirstCommentResult — Outcome of an auto first-comment attempt, written after the main post publishes.
        - `status` 'posted' | 'failed' | 'skipped' — `posted` = comment is live; `failed` = the main post published but the comment did not (see `error`; retry via POST /posts/retry-first-comment); `skipped` = the main post did not publish so the comment was never attempted.
        - `pending` boolean, nullable — TikTok only. `true` while the comment waits for TikTok to return the final video id (status is `failed` with an explanatory error meanwhile); the comment is posted automatically once the id resolves, usually within minutes.
        - `comment_id` string, nullable
        - `url` string, nullable
        - `error` string, nullable
        - `posted_at` string, date-time, nullable
    - `linkedin` object, nullable — LinkedIn Profile options on the post — `first_comment`, its post-publish `first_comment_result`, and `carousel_as_images` (present when the post opts out of the default PDF document carousel).
      - `first_comment` string
      - `first_comment_result` FirstCommentResult — Outcome of an auto first-comment attempt, written after the main post publishes.
        - `status` 'posted' | 'failed' | 'skipped' — `posted` = comment is live; `failed` = the main post published but the comment did not (see `error`; retry via POST /posts/retry-first-comment); `skipped` = the main post did not publish so the comment was never attempted.
        - `pending` boolean, nullable — TikTok only. `true` while the comment waits for TikTok to return the final video id (status is `failed` with an explanatory error meanwhile); the comment is posted automatically once the id resolves, usually within minutes.
        - `comment_id` string, nullable
        - `url` string, nullable
        - `error` string, nullable
        - `posted_at` string, date-time, nullable
      - `carousel_as_images` boolean
    - `linkedin_page` object, nullable — LinkedIn Company Page options on the post — `first_comment`, its post-publish `first_comment_result`, and `carousel_as_images` (present when the post opts out of the default PDF document carousel).
      - `first_comment` string
      - `first_comment_result` FirstCommentResult — Outcome of an auto first-comment attempt, written after the main post publishes.
        - `status` 'posted' | 'failed' | 'skipped' — `posted` = comment is live; `failed` = the main post published but the comment did not (see `error`; retry via POST /posts/retry-first-comment); `skipped` = the main post did not publish so the comment was never attempted.
        - `pending` boolean, nullable — TikTok only. `true` while the comment waits for TikTok to return the final video id (status is `failed` with an explanatory error meanwhile); the comment is posted automatically once the id resolves, usually within minutes.
        - `comment_id` string, nullable
        - `url` string, nullable
        - `error` string, nullable
        - `posted_at` string, date-time, nullable
      - `carousel_as_images` boolean
    - `youtube` object, nullable — YouTube options on the post. After publish, `first_comment` and its `first_comment_result` are nested here alongside the Shorts metadata.
      - `first_comment` string
      - `first_comment_result` FirstCommentResult — Outcome of an auto first-comment attempt, written after the main post publishes.
        - `status` 'posted' | 'failed' | 'skipped' — `posted` = comment is live; `failed` = the main post published but the comment did not (see `error`; retry via POST /posts/retry-first-comment); `skipped` = the main post did not publish so the comment was never attempted.
        - `pending` boolean, nullable — TikTok only. `true` while the comment waits for TikTok to return the final video id (status is `failed` with an explanatory error meanwhile); the comment is posted automatically once the id resolves, usually within minutes.
        - `comment_id` string, nullable
        - `url` string, nullable
        - `error` string, nullable
        - `posted_at` string, date-time, nullable
    - `tiktok` object, nullable — TikTok options on the post. After publish, `first_comment` and its `first_comment_result` are nested here alongside the TikTok publish options. First comments need the workspace's TikTok comments authorization.
      - `title` string, nullable — Photo Mode title (photo carousels only, max 90 characters). Absent when no title was set.
      - `first_comment` string
      - `first_comment_result` FirstCommentResult — Outcome of an auto first-comment attempt, written after the main post publishes.
        - `status` 'posted' | 'failed' | 'skipped' — `posted` = comment is live; `failed` = the main post published but the comment did not (see `error`; retry via POST /posts/retry-first-comment); `skipped` = the main post did not publish so the comment was never attempted.
        - `pending` boolean, nullable — TikTok only. `true` while the comment waits for TikTok to return the final video id (status is `failed` with an explanatory error meanwhile); the comment is posted automatically once the id resolves, usually within minutes.
        - `comment_id` string, nullable
        - `url` string, nullable
        - `error` string, nullable
        - `posted_at` string, date-time, nullable

## Other responses

- `400` — Validation error

---

[API](https://skmtc.dev/omnisocials/apis/omnisocials-api.md) · [All operations](https://skmtc.dev/omnisocials/apis/omnisocials-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/omnisocials/omnisocials-api/revisions/e53b89af0b98/schema)
