---
title: "Create a post"
method: POST
path: "/api/v1/posts"
tags: ["Posts"]
---

# Create a post

`POST /api/v1/posts`

## Request

- Base URL: `https://www.poster.ly`
- URL: `https://www.poster.ly/api/v1/posts`
- Auth: HTTP bearer

Create a scheduled or immediate post.
Set `dry_run: true` to validate first. Dry-run returns normalized fields and warnings without creating a post, copying remote media, consuming post-item/storage quota, reserving X quota, emitting webhooks, or enqueueing publication. Authentication bookkeeping, request rate limits, API usage recording, and read-only validation still occur.
An `Idempotency-Key` header is ignored during dry-run. Third-party media is checked but not copied; the response includes `remote_media_not_materialized` because live create performs storage copy and final byte validation.

For X (`platform: twitter`):
- Managed X posting is limited per workspace monthly X posting cycle.
- URLs are blocked in the caption and any `metadata.x_thread_tweets` segment, including bare domains like `poster.ly`.
- Each X thread segment counts as one managed X post toward the monthly allowance.
- Call `GET /api/v1/x-posting/quota` to inspect remaining allowance before scheduling.
- Articles: pass `settings.article` (`{ title, cover?: "first_image" | "none" }`) to publish the caption as an X Article. The caption is the Article body, written as Markdown, and attached images can be placed with `![caption](media:1)`. The X account needs X Premium. Articles cannot be combined with a poll, thread, paid partnership, video, or GIF, and the 280-character post limit does not apply. An Article counts as one managed X post.

Threads (X / Twitter and Threads / Meta):
- For X, set `post_type: "x_thread"` and `metadata.x_thread_tweets: string[]` (2+ entries).
- For Threads, set `post_type: "threads_thread"` and `metadata.threads_thread_posts: string[]` (2+ entries).
- The caption stored on the post is always the first array entry; any caption you also pass is ignored.
- Media (`media_url` / `media_urls`) attaches to the first post only.
- Per-segment length is validated up front: 280 chars for X (4000 verified, 25000 organization), 500 chars for Threads.

Instagram settings:
- Pass `settings.__type: "instagram"` or `"instagram-standalone"`.
- `settings.post_type` accepts `post`/`feed`, `story`, `reel`, or `carousel`.
- A caption `@username` is a caption mention only. Use `settings.user_tags` for native clickable media tags and `settings.collaborators` for co-author invitations.
- Native user tags work with eligible direct Instagram and Meta-linked accounts. Collaborators require an eligible Meta-linked Instagram account. `GET /api/v1/accounts` and the account-specific schema expose both capabilities.
- Native user tags support feed images, Reels/video, and Stories. Carousel tags are applied to each child item. Collaborators support feed images, Reels, and carousels, but not Stories.
- Use `settings.first_comment`, `settings.media_accessibility` (preferred), `settings.media_alt_texts` (legacy), `settings.reel_cover_url`, and `settings.reel_thumb_offset` for other composer-equivalent controls.
- Trial Reels can be requested with `settings.is_trial_reel: true`; `settings.graduation_strategy` defaults to `"MANUAL"` and also accepts `"SS_PERFORMANCE"`.
- Caption add-ons: `settings.caption_addon` (`type` `poll` or `comment_prompt`), or `settings.poll_attachment` / `settings.is_comment_prompt_used`. Facebook Login only. The caption is required and is the poll question or the prompt. Not available on Stories. On a carousel, set the add-on on the parent only. A poll and a comment prompt cannot be combined. `is_poll_duration_extended` is only valid with a poll. The add-on is written once when the media container is created.
- Meta controls Trial Reel availability per Instagram account. If the Trial toggle does not appear for that account in Instagram, Meta may reject API publishing.

Other platform settings:
- YouTube: title/privacy/thumbnail/tags/category.
- LinkedIn: document title/filename, organization mentions, video thumbnail, media alt text.
- TikTok: a single video or a photo slideshow of 1 to 35 images. Image media auto-detects as a slideshow, so `post_type` and `media_type` are optional for photos and every image is posted; set `post_type: "carousel"` (2 or more images) or `"image"` (one), or `settings.media_type: "PHOTO"`, to be explicit. Also supports direct-post privacy, comment/duet/stitch toggles, title, commercial disclosure, and `settings.video_cover_timestamp_ms` to pick the video frame used as the cover (direct-post videos only; TikTok does not accept custom uploaded covers).
- Pinterest: board, title, destination link.
- Google Business Profile: standard/event/offer post type, event schedule, offer details, CTA.
- Facebook: post/story/reel/cover-photo intent and colored text background preset. Reels through the Graph Reels API must be 90 seconds or shorter; use video for longer clips.
- X: reply settings, polls, media alt text.
- Threads: reply controls, long-form text attachments, media alt text.
- Bluesky: languages and media alt text.

## Headers

- `Idempotency-Key` string

## Request body

- CreatePostRequest
  - `account_id` string: Social account ID
  - `username` string: Alternative account lookup by username + platform
  - `platform` string
  - `caption` string, nullable: Post caption. On Instagram, @username creates a caption mention only; it does not create a native media tag or collaborator invitation. For X, URLs are currently blocked in the caption and thread segments.
  - `scheduled_at` string, date-time, nullable
  - `post_type` 'text' | 'image' | 'document' | 'video' | 'reel' | 'carousel' | 'story' | 'story_series' | 'x_thread' | 'threads_thread' | 'photo' | 'cover_photo' | 'post' | 'feed' | 'feed-post' | 'cover-photo' | 'photos' | 'stories' | 'reels', nullable: Optional post type. Omit for auto-detection from media. Use `x_thread` (twitter) or `threads_thread` (threads) to schedule a multi-post reply chain. `photo` is normalized for TikTok photo posts; `cover_photo` is a Facebook-only semantic type.
  - `media_url` string, nullable: Media URL. Posterly media URLs returned by upload endpoints are used as-is. Third-party HTTP(S) URLs are copied into posterly storage before the post is saved, so short-lived signed URLs must still be live when the request runs.
  - `media_urls` string[], nullable: Media URLs. Posterly media URLs are used as-is; third-party HTTP(S) URLs are copied into posterly storage before the post is saved. For TikTok, 2 or more images auto-create a photo slideshow of up to 35 images and every image is posted.
  - `metadata` object, nullable: Platform-specific metadata. Prefer `settings` for first-class platform settings such as Instagram collaborators, stories, trial Reels, first comments, alt text, and Reel covers. For X threads, pass `x_thread_tweets` as an array of 2+ tweet strings. The first entry leads the thread; the rest are published as replies in order. Each segment must fit the X character limit (280 standard, 4000 verified, 25000 organization) and cannot contain a URL. Each segment counts as one managed X post toward the monthly allowance. For Threads threads, pass `threads_thread_posts` as an array of 2+ post strings (each ≤ 500 characters). Same chain semantics: first leads, rest are self-replies.
  - `settings` union: Platform-specific settings. Prefer this over raw `metadata` so the API can map fields into posterly composer/publisher metadata.
    - InstagramPostSettings: Instagram-specific create/update settings. These map to the same Instagram scheduling controls available in the posterly composer.
      - `__type` 'instagram' | 'instagram-standalone': Use `instagram` for Meta/Facebook-linked Instagram accounts and `instagram-standalone` for standalone Instagram accounts.
      - `post_type` 'post' | 'feed' | 'story' | 'reel' | 'carousel': `post`/`feed` publishes a regular feed post. Use `story`, `reel`, or `carousel` for those formats.
      - `is_trial_reel` boolean: Publish a video as an Instagram Trial Reel when supported by the connected account. Requires `post_type: "reel"` or a video feed post that resolves to a Reel. Meta controls account availability; if the Trial toggle does not appear for the account in Instagram, publishing may fail.
      - `graduation_strategy` 'MANUAL' | 'SS_PERFORMANCE': Trial Reel graduation strategy. `MANUAL` keeps promotion to followers inside the Instagram app; `SS_PERFORMANCE` lets Meta auto-share if the trial performs well.
      - `collaborators` union[]: Native co-author invitations, not caption mentions or media tags. Meta-linked Instagram accounts only; up to 3; feed images, Reels, and carousels; not Stories.
        - union
          - string
          - object
            - `label` string, required
      - `user_tags` union[]: Native clickable media tags, not caption @mentions. Supported by eligible direct Instagram and Meta-linked connections; feed images, Reels/video, and Stories. Tags apply to each carousel child and the first Story in a Story Series.
        - union
          - string
          - object
            - `username` string, required
      - `first_comment` string: Optional first comment to post after the Instagram post is published.
      - `media_alt_texts` object: Legacy map of each source media URL to alt text. Still accepted for backwards compatibility.
      - `media_accessibility` object[]: Preferred ordered accessibility metadata. Stable media identity survives platform image preparation.
        - `index` integer, required
        - `asset_id` string
        - `source_url` string, uri
        - `alt_text` string, required
      - `reel_cover_url` string: Custom cover image URL for Instagram Reels.
      - `reel_thumb_offset` number: Video timestamp offset, in milliseconds, to use as the Instagram Reel thumbnail when no cover URL is supplied.
      - `caption_addon` object, nullable: Optional caption add-on. `type` is `none`, `poll`, or `comment_prompt`. A poll needs 2 to 4 `options` of 1 to 25 characters (an emoji counts as one) and the caption is the question. A comment prompt uses the caption as the prompt. Facebook Login only. Not available on Stories. Carousel parent only. At most one add-on. Pass null or `type: "none"` to clear. Write-once when the media container is created.
        - `type` 'none' | 'poll' | 'comment_prompt'
        - `options` string[]
        - `extended_duration` boolean: Longer poll duration. Only valid with type poll.
      - `poll_attachment` object: Poll choices. `option_a` and `option_b` are required. `option_c` and `option_d` are optional, and `option_d` requires `option_c`. Serialised JSON must be 200 characters or fewer. Mutually exclusive with `is_comment_prompt_used`. The caption is the question.
        - `option_a` string
        - `option_b` string
        - `option_c` string
        - `option_d` string
        - `options` string[]
      - `is_comment_prompt_used` boolean: Attach a comment prompt. The caption is the prompt. Mutually exclusive with `poll_attachment`. Facebook Login only. Not available on Stories.
      - `is_poll_duration_extended` boolean: Request the longer poll duration. Only valid together with a poll.
    - union: Platform-specific composer controls. Call list_platforms (view=schema with the selected account_id) before sending settings. Supported platforms are sourced from the public platform registry. Planned possibilities currently tracked: reddit, medium, skool, whop.
      - object
        - `__type` 'instagram' | 'instagram-standalone': Instagram account type hint.
        - `post_type` 'post' | 'feed' | 'story' | 'reel' | 'carousel': Instagram publishing format.
        - `is_trial_reel` boolean: Request an Instagram Trial Reel when the account supports it.
        - `trial_reel` boolean: Request an Instagram Trial Reel when the account supports it. Alias for is_trial_reel.
        - `graduation_strategy` 'MANUAL' | 'SS_PERFORMANCE': Trial Reel graduation strategy.
        - `collaborators` unknown[]: Native co-author invitations. Meta-linked Instagram accounts only; feed images, Reels, and carousels; maximum 3; not Stories.
          - unknown
        - `user_tags` unknown[]: Native clickable media tags, not caption @mentions. Supported by eligible direct Instagram and Meta-linked connections; feed images, Reels/video, and Stories. Tags apply to each carousel child and the first Story in a Story Series.
          - unknown
        - `first_comment` string: First comment to publish after the post.
        - `reel_cover_url` string: Custom Reel cover image URL.
        - `cover_url` string: Custom Reel cover image URL. Alias for reel_cover_url.
        - `reel_thumb_offset` number: Video timestamp, in milliseconds, for a generated Reel thumbnail.
        - `thumb_offset` number: Video timestamp, in milliseconds, for a generated Reel thumbnail. Alias for reel_thumb_offset.
        - `is_ai_generated` boolean: Label the parent media container as AI-generated (is_ai_generated). For carousels, set this on the parent only, never on child slides.
        - `instagram_is_ai_generated` boolean: Label the parent media container as AI-generated (is_ai_generated). For carousels, set this on the parent only, never on child slides. Alias for is_ai_generated.
        - `ai_generated` boolean: Label the parent media container as AI-generated (is_ai_generated). For carousels, set this on the parent only, never on child slides. Alias for is_ai_generated.
        - `audio_id` string: Licensed Instagram audio ID for Reels (audio_configuration.audio_id). Meta-linked Facebook Login accounts only; hidden for Instagram Login.
        - `instagram_audio_id` string: Licensed Instagram audio ID for Reels (audio_configuration.audio_id). Meta-linked Facebook Login accounts only; hidden for Instagram Login. Alias for audio_id.
        - `caption_addon` object: Optional caption add-on. type is none, poll, or comment_prompt. A poll needs 2 to 4 options (1 to 25 characters each; an emoji counts as one) and the caption is the question. A comment prompt uses the caption as the prompt. Facebook Login only. Not Stories. Carousel parent only. At most one add-on. Pass null or type none to clear.
        - `instagram_caption_addon` object: Optional caption add-on. type is none, poll, or comment_prompt. A poll needs 2 to 4 options (1 to 25 characters each; an emoji counts as one) and the caption is the question. A comment prompt uses the caption as the prompt. Facebook Login only. Not Stories. Carousel parent only. At most one add-on. Pass null or type none to clear. Alias for caption_addon.
        - `poll_attachment` object: Poll choices: option_a and option_b required, option_c and option_d optional, option_d requires option_c. Serialised JSON must be 200 characters or fewer. Mutually exclusive with is_comment_prompt_used. The caption is the question.
        - `instagram_poll_attachment` object: Poll choices: option_a and option_b required, option_c and option_d optional, option_d requires option_c. Serialised JSON must be 200 characters or fewer. Mutually exclusive with is_comment_prompt_used. The caption is the question. Alias for poll_attachment.
        - `is_comment_prompt_used` boolean: Attach a comment prompt. The caption is the prompt. Mutually exclusive with poll_attachment. Facebook Login only. Not Stories.
        - `instagram_is_comment_prompt_used` boolean: Attach a comment prompt. The caption is the prompt. Mutually exclusive with poll_attachment. Facebook Login only. Not Stories. Alias for is_comment_prompt_used.
        - `is_poll_duration_extended` boolean: Request the longer poll duration. Only valid together with a poll.
        - `instagram_is_poll_duration_extended` boolean: Request the longer poll duration. Only valid together with a poll. Alias for is_poll_duration_extended.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `post_type` 'post' | 'feed' | 'story' | 'reel' | 'cover_photo': Facebook publishing format. Reel uses the Graph Reels API and must be 90 seconds or shorter; use video for longer clips. A 90.0 second Reel is allowed.
        - `text_format_preset_id` string: Styled text post background preset.
        - `background_preset_id` string: Styled text post background preset. Alias for text_format_preset_id.
        - `reel_cover_url` string: Custom Reel cover image URL.
        - `cover_url` string: Custom Reel cover image URL. Alias for reel_cover_url.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `privacy_level` string: TikTok direct-post privacy level from the creator info helper.
        - `allow_comment` boolean: Allow comments when direct posting.
        - `allow_duet` boolean: Allow duets when direct posting.
        - `allow_stitch` boolean: Allow stitches when direct posting.
        - `title` string: For a video, the title is the first line of the caption when the title and the caption both fit within 2,200 characters. For a photo post, it is a 90-character title.
        - `media_type` 'PHOTO': Set to PHOTO for TikTok photo posts or multi-photo slideshows.
        - `photo_cover_index` number: Zero-based cover image index for TikTok photo posts.
        - `video_cover_timestamp_ms` number: Millisecond timestamp of the video frame to use as the cover (direct post only).
        - `cover_timestamp_ms` number: Millisecond timestamp of the video frame to use as the cover (direct post only). Alias for video_cover_timestamp_ms.
        - `auto_add_music` boolean: Ask TikTok to add recommended music to photo posts when direct posting.
        - `is_commercial_content` boolean: TikTok commercial content disclosure.
        - `is_your_brand` boolean: TikTok branded content: your own brand.
        - `is_branded_content` boolean: TikTok branded content: third-party brand.
        - `ai_generated` boolean: Label the post as AI-generated (TikTok is_aigc).
        - `ai_disclosure` boolean: Label the post as AI-generated (TikTok is_aigc). Alias for ai_generated.
      - object
        - `reply_settings` 'everyone' | 'following' | 'mentionedUsers' | 'subscribers' | 'verified': Who can reply to the post.
        - `x_reply_settings` 'everyone' | 'following' | 'mentionedUsers' | 'subscribers' | 'verified': Who can reply to the post. Alias for reply_settings.
        - `poll` object: X poll settings: options and duration_minutes.
        - `x_poll` object: X poll settings: options and duration_minutes. Alias for poll.
        - `article` object: Publish as an X Article: { title, cover?: "first_image" | "none" }. The post caption is the Article body, written as Markdown (headings, bold, italic, lists, quotes, code blocks, tables, dividers). Reference attached images with ![caption](media:1). Needs an X Premium account. Not combinable with poll, thread, paid_partnership or video/GIF. Pass null to turn Article mode off.
        - `x_article` object: Publish as an X Article: { title, cover?: "first_image" | "none" }. The post caption is the Article body, written as Markdown (headings, bold, italic, lists, quotes, code blocks, tables, dividers). Reference attached images with ![caption](media:1). Needs an X Premium account. Not combinable with poll, thread, paid_partnership or video/GIF. Pass null to turn Article mode off. Alias for article.
        - `ai_generated` boolean: Label the post as AI-generated (X made_with_ai).
        - `ai_disclosure` boolean: Label the post as AI-generated (X made_with_ai). Alias for ai_generated.
        - `made_with_ai` boolean: Label the post as AI-generated (X made_with_ai). Alias for ai_generated.
        - `paid_partnership` boolean: Mark the post as a paid partnership (POST /2/tweets paid_partnership). Existing tweet.write.
        - `x_paid_partnership` boolean: Mark the post as a paid partnership (POST /2/tweets paid_partnership). Existing tweet.write. Alias for paid_partnership.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `document_title` string: Title for a LinkedIn PDF/document post.
        - `linkedin_document_title` string: Title for a LinkedIn PDF/document post. Alias for document_title.
        - `document_filename` string: Filename for a LinkedIn document post.
        - `mentions` unknown[]: LinkedIn organization mention objects.
          - unknown
        - `linkedin_mentions` unknown[]: LinkedIn organization mention objects. Alias for mentions.
          - unknown
        - `poll` object: LinkedIn poll: { question, options (2-4), duration (ONE_DAY|THREE_DAYS|SEVEN_DAYS|FOURTEEN_DAYS) }. Mutually exclusive with media.
        - `linkedin_poll` object: LinkedIn poll: { question, options (2-4), duration (ONE_DAY|THREE_DAYS|SEVEN_DAYS|FOURTEEN_DAYS) }. Mutually exclusive with media. Alias for poll.
        - `thumbnail_url` string: Custom video thumbnail URL.
        - `content_call_to_action_label` 'APPLY' | 'DOWNLOAD' | 'VIEW_QUOTE' | 'LEARN_MORE' | 'SIGN_UP' | 'SUBSCRIBE' | 'REGISTER' | 'JOIN' | 'ATTEND' | 'REQUEST_DEMO' | 'SEE_MORE' | 'BUY_NOW' | 'SHOP_NOW': LinkedIn contentCallToActionLabel, including BUY_NOW and SHOP_NOW (version 202504). Pair with content_landing_page.
        - `cta_label` 'APPLY' | 'DOWNLOAD' | 'VIEW_QUOTE' | 'LEARN_MORE' | 'SIGN_UP' | 'SUBSCRIBE' | 'REGISTER' | 'JOIN' | 'ATTEND' | 'REQUEST_DEMO' | 'SEE_MORE' | 'BUY_NOW' | 'SHOP_NOW': LinkedIn contentCallToActionLabel, including BUY_NOW and SHOP_NOW (version 202504). Pair with content_landing_page. Alias for content_call_to_action_label.
        - `linkedin_cta_label` 'APPLY' | 'DOWNLOAD' | 'VIEW_QUOTE' | 'LEARN_MORE' | 'SIGN_UP' | 'SUBSCRIBE' | 'REGISTER' | 'JOIN' | 'ATTEND' | 'REQUEST_DEMO' | 'SEE_MORE' | 'BUY_NOW' | 'SHOP_NOW': LinkedIn contentCallToActionLabel, including BUY_NOW and SHOP_NOW (version 202504). Pair with content_landing_page. Alias for content_call_to_action_label.
        - `content_landing_page` string: Landing page URL sent as contentLandingPage with the LinkedIn call to action.
        - `cta_url` string: Landing page URL sent as contentLandingPage with the LinkedIn call to action. Alias for content_landing_page.
        - `linkedin_cta_url` string: Landing page URL sent as contentLandingPage with the LinkedIn call to action. Alias for content_landing_page.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `title` string, required: YouTube video title.
        - `youtube_title` string: YouTube video title. Alias for title.
        - `thumbnail_url` string: Custom YouTube thumbnail URL.
        - `privacy_status` 'public' | 'unlisted' | 'private': YouTube video visibility.
        - `privacy` 'public' | 'unlisted' | 'private': YouTube video visibility. Alias for privacy_status.
        - `made_for_kids` boolean: YouTube made-for-kids declaration.
        - `tags` string[]: YouTube tags.
        - `category_id` string: YouTube category ID.
        - `notify_subscribers` boolean: Notify subscribers when the video publishes (default true).
        - `add_shorts_hashtag` boolean: Append #Shorts to the description for Shorts-feed eligibility (vertical clips).
        - `ai_generated` boolean: Label the video as containing AI/synthetic media (containsSyntheticMedia).
        - `ai_disclosure` boolean: Label the video as containing AI/synthetic media (containsSyntheticMedia). Alias for ai_generated.
        - `brand_partner` string: Optional YouTube brandPartner channel ID (UC…) or handle (@…). Added to videos.insert part=brandPartner when set.
        - `brandPartner` string: Optional YouTube brandPartner channel ID (UC…) or handle (@…). Added to videos.insert part=brandPartner when set. Alias for brand_partner.
      - object
        - `board_id` string, required: Pinterest board ID from the pinterest.boards helper.
        - `pinterest_board_id` string: Pinterest board ID from the pinterest.boards helper. Alias for board_id.
        - `title` string: Pinterest Pin title.
        - `link` string: Destination URL for the Pin.
        - `destination_url` string: Destination URL for the Pin. Alias for link.
        - `cover_image_url` string: Required for video Pins: a stored, probe-verified JPEG or PNG cover URL (20 MiB maximum).
        - `video_cover_url` string: Required for video Pins: a stored, probe-verified JPEG or PNG cover URL (20 MiB maximum). Alias for cover_image_url.
        - `pinterest_cover_image_url` string: Required for video Pins: a stored, probe-verified JPEG or PNG cover URL (20 MiB maximum). Alias for cover_image_url.
        - `ai_generated` boolean: Label the Pin as AI-modified (ai_disclosures).
        - `ai_disclosure` boolean: Label the Pin as AI-modified (ai_disclosures). Alias for ai_generated.
        - `product_tags` unknown[]: Optional product tags on POST /v5/pins (product_id, optional x/y). Existing pins:write. Pin note is not written.
          - unknown
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `reply_control` 'everyone' | 'accounts_you_follow' | 'mentioned_only' | 'followers_only' | 'parent_post_author_only': Threads reply control setting.
        - `threads_reply_control` 'everyone' | 'accounts_you_follow' | 'mentioned_only' | 'followers_only' | 'parent_post_author_only': Threads reply control setting. Alias for reply_control.
        - `poll` object: Threads poll: { options (2-4) }. Text-only, fixed 24h duration.
        - `threads_poll` object: Threads poll: { options (2-4) }. Text-only, fixed 24h duration. Alias for poll.
        - `topic_tag` string: A single Threads topic tag (1-50 chars, no periods/ampersands).
        - `threads_topic_tag` string: A single Threads topic tag (1-50 chars, no periods/ampersands). Alias for topic_tag.
        - `text_attachment` object: Long-form Threads text attachment.
        - `threads_text_attachment` object: Long-form Threads text attachment. Alias for text_attachment.
        - `share_to_instagram` boolean: Also share the published post as a Story to the Instagram account linked to the Threads profile. Requires a linked Instagram account and sharing permission. The Threads post can publish even if Story sharing fails.
        - `threads_share_to_instagram` boolean: Also share the published post as a Story to the Instagram account linked to the Threads profile. Requires a linked Instagram account and sharing permission. The Threads post can publish even if Story sharing fails. Alias for share_to_instagram.
        - `is_ghost_post` boolean: Publish as a Threads ghost post. Text-only.
        - `threads_is_ghost_post` boolean: Publish as a Threads ghost post. Text-only. Alias for is_ghost_post.
        - `is_spoiler` boolean: Mark the Threads post as a spoiler (full-caption text entity and/or is_spoiler_media).
        - `threads_is_spoiler` boolean: Mark the Threads post as a spoiler (full-caption text entity and/or is_spoiler_media). Alias for is_spoiler.
        - `is_spoiler_media` boolean: Set is_spoiler_media on the Threads container when media is attached.
        - `threads_is_spoiler_media` boolean: Set is_spoiler_media on the Threads container when media is attached. Alias for is_spoiler_media.
        - `text_entities` unknown[]: Threads text_entities, for example spoiler ranges with entity_type, offset, and length.
          - unknown
        - `threads_text_entities` unknown[]: Threads text_entities, for example spoiler ranges with entity_type, offset, and length. Alias for text_entities.
          - unknown
        - `enable_reply_approvals` boolean: Require approval before replies appear (enable_reply_approvals).
        - `threads_enable_reply_approvals` boolean: Require approval before replies appear (enable_reply_approvals). Alias for enable_reply_approvals.
        - `gif_attachment` object: GIPHY gif_attachment: { gif_id, provider: "GIPHY" }. Tenor is not supported.
        - `threads_gif_attachment` object: GIPHY gif_attachment: { gif_id, provider: "GIPHY" }. Tenor is not supported. Alias for gif_attachment.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `post_type` 'STANDARD' | 'EVENT' | 'OFFER': Google Business Profile post type.
        - `topic_type` 'STANDARD' | 'EVENT' | 'OFFER': Google Business Profile post type. Alias for post_type.
        - `event` object: Event title and start/end date/time.
        - `offer` object: Offer coupon, redeem URL, and terms.
        - `cta` object: CTA type and URL.
        - `recurrence` object: EVENT/OFFER recurrenceInfo: { type: DAILY|WEEKLY|MONTHLY, series_end_time? }. Keeps one-off scheduledTime.
        - `recurrenceInfo` object: EVENT/OFFER recurrenceInfo: { type: DAILY|WEEKLY|MONTHLY, series_end_time? }. Keeps one-off scheduledTime. Alias for recurrence.
      - object
        - `parse_mode` 'HTML' | 'MarkdownV2': Telegram caption parse mode when supported.
        - `disable_web_page_preview` boolean: Disable link previews for text posts.
        - `poll` object: Telegram native poll: { question, options (2-10), allows_multiple_answers? }.
        - `telegram_poll` object: Telegram native poll: { question, options (2-10), allows_multiple_answers? }. Alias for poll.
        - `buttons` unknown[]: Inline CTA buttons: [{ text, url }] (up to 3).
          - unknown
        - `telegram_buttons` unknown[]: Inline CTA buttons: [{ text, url }] (up to 3). Alias for buttons.
          - unknown
        - `video_cover_url` string: Cover image URL for sendVideo (Bot API cover).
        - `cover_url` string: Cover image URL for sendVideo (Bot API cover). Alias for video_cover_url.
        - `telegram_video_cover_url` string: Cover image URL for sendVideo (Bot API cover). Alias for video_cover_url.
        - `start_timestamp` number: Video start timestamp in seconds for sendVideo (Bot API start_timestamp).
        - `telegram_start_timestamp` number: Video start timestamp in seconds for sendVideo (Bot API start_timestamp). Alias for start_timestamp.
        - `is_live_photo` boolean: Send as sendLivePhoto when both a still image and a video are attached.
        - `telegram_is_live_photo` boolean: Send as sendLivePhoto when both a still image and a video are attached. Alias for is_live_photo.
      - object
        - `langs` string[]: Language tags for the Bluesky post.
        - `content_label` 'sexual' | 'nudity' | 'porn' | 'graphic-media': Self-applied content warning (tap-to-reveal).
        - `content_warning` 'sexual' | 'nudity' | 'porn' | 'graphic-media': Self-applied content warning (tap-to-reveal). Alias for content_label.
        - `tags` string[]: Up to 8 hidden discovery tags (separate from in-text hashtags).
        - `bluesky_tags` string[]: Up to 8 hidden discovery tags (separate from in-text hashtags). Alias for tags.
        - `quote_url` string: A Bluesky post URL (or AT-URI) to quote.
        - `quote` string: A Bluesky post URL (or AT-URI) to quote. Alias for quote_url.
        - `bluesky_quote_url` string: A Bluesky post URL (or AT-URI) to quote. Alias for quote_url.
        - `disable_quotes` boolean: Prevent others from quote-posting this post (postgate).
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `markdown` boolean: Send the caption as a Block Kit markdown block on chat.postMessage.
        - `slack_markdown` boolean: Send the caption as a Block Kit markdown block on chat.postMessage. Alias for markdown.
        - `blocks` unknown[]: Optional Block Kit blocks (markdown, card, or carousel) passed to chat.postMessage. File upload still uses getUploadURLExternal.
          - unknown
        - `slack_blocks` unknown[]: Optional Block Kit blocks (markdown, card, or carousel) passed to chat.postMessage. File upload still uses getUploadURLExternal. Alias for blocks.
          - unknown
      - object
        - `visibility` 'public' | 'unlisted' | 'private': Status visibility (private means followers only).
        - `mastodon_visibility` 'public' | 'unlisted' | 'private': Status visibility (private means followers only). Alias for visibility.
        - `spoiler_text` string: Content warning shown before the post is revealed (marks media sensitive).
        - `content_warning` string: Content warning shown before the post is revealed (marks media sensitive). Alias for spoiler_text.
        - `mastodon_spoiler_text` string: Content warning shown before the post is revealed (marks media sensitive). Alias for spoiler_text.
        - `language` string: ISO 639 language code for the status.
        - `mastodon_language` string: ISO 639 language code for the status. Alias for language.
        - `quoted_status_id` string: Mastodon status ID to quote (4.5+ quoted_status_id).
        - `quote_id` string: Mastodon status ID to quote (4.5+ quoted_status_id). Alias for quoted_status_id.
        - `mastodon_quoted_status_id` string: Mastodon status ID to quote (4.5+ quoted_status_id). Alias for quoted_status_id.
        - `poll` object: Mastodon poll: { options (2-4), expires_in seconds, multiple? }. Media and a poll can be combined on Mastodon 4.6+.
        - `mastodon_poll` object: Mastodon poll: { options (2-4), expires_in seconds, multiple? }. Media and a poll can be combined on Mastodon 4.6+. Alias for poll.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `title` string: Article title. Falls back to the first line of the caption.
        - `devto_title` string: Article title. Falls back to the first line of the caption. Alias for title.
        - `tags` string[]: Up to 4 tags (lowercase alphanumeric).
        - `devto_tags` string[]: Up to 4 tags (lowercase alphanumeric). Alias for tags.
        - `canonical_url` string: Canonical URL when cross-posting from your own blog.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `title` string: Post title. Falls back to the first line of the caption.
        - `hashnode_title` string: Post title. Falls back to the first line of the caption. Alias for title.
        - `tags` string[]: Up to 4 tags (lowercase alphanumeric).
        - `hashnode_tags` string[]: Up to 4 tags (lowercase alphanumeric). Alias for tags.
        - `publication_id` string: Target publication. Defaults to the account blog captured at connect time.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `title` string: Post title. Falls back to the first line of the caption.
        - `wordpress_title` string: Post title. Falls back to the first line of the caption. Alias for title.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
      - object
        - `community` string, required: Target community, e.g. technology or technology@lemmy.world.
        - `lemmy_community` string: Target community, e.g. technology or technology@lemmy.world. Alias for community.
        - `title` string: Post title. Falls back to the first line of the caption.
        - `lemmy_title` string: Post title. Falls back to the first line of the caption. Alias for title.
        - `url` string: Link post URL. Defaults to the first attached image.
        - `lemmy_url` string: Link post URL. Defaults to the first attached image. Alias for url.
        - `nsfw` boolean: Mark the post NSFW.
        - `media_alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility.
        - `alt_texts` object: Legacy map of source media URLs to alt text. Still accepted for backwards compatibility. Alias for media_alt_texts.
        - `media_accessibility` object[]: Ordered media accessibility entries: { index, asset_id?, source_url?, alt_text }. Preferred because identity survives platform media preparation.
          - `index` integer, required
          - `asset_id` string
          - `source_url` string, uri
          - `alt_text` string, required
  - `reel_cover_url` string, nullable: Custom cover image URL for Instagram/Facebook Reels. Also accepted as `settings.reel_cover_url` for Instagram.
  - `reel_cover_method` string, nullable: Optional source label for the Reel cover, such as `api`, `upload`, or `frame`.
  - `reel_thumb_offset` number, nullable: Video timestamp offset, in milliseconds, to use as the Reel thumbnail when no cover URL is supplied.
  - `workspace_id` string, nullable: Optional workspace to create the post inside. Defaults to the account workspace, then the personal workspace.
  - `dry_run` boolean: When true, validates and normalizes the post without creating records, copying remote media, consuming post-item or storage quota, reserving X quota, emitting webhooks, or enqueueing publication. Authentication bookkeeping, request rate limits, API usage recording, and read-only validation still occur. Validate before the first live create.
  - `group_id` string, nullable: Optional caller-defined group ID for campaign/batch cleanup. Also accepted as api_group_id or post_group_id.
  - `release_id` string, nullable: Optional caller-defined release ID for external repair/relink workflows.

## Response `200`

Dry-run validation result, or an existing post returned for a replayed idempotent request

- union
  - ValidatePostResponse
    - `dry_run` true, required
    - `valid` true, required
    - `normalized` object, required
      - `account_id` integer, required
      - `platform` string, required
      - `post_type` string, required
      - `scheduled_at` string, date-time, required
      - `workspace_id` string, nullable, required
    - `warnings` object[], required
      - `code` 'remote_media_not_materialized', required
      - `message` string, required
    - `would_create` object, required
      - `status` string, required
  - CreatePostResponse
    - `post` object, required
      - `id` integer, required
      - `status` string, required
      - `scheduled_at` string, date-time, required
      - `platform` string, required
      - `post_type` string, required
      - `workspace_id` string, nullable
      - `workspace_client_id` string, uuid, nullable
    - `workspace` object, required
      - `id` string, required
      - `name` string, required
      - `is_personal` boolean, required
      - `resolved_from` string, required
    - `warnings` object[]: Present only when a non-blocking warning applies. The post was still created.
      - `code` 'tiktok_pending_inbox_cap', required
      - `message` string, required

## Other responses

- `201`: Post created successfully
- `400`: Validation error
- `401`: Invalid or missing API key
- `403`: The caller is a member of the account's workspace but below the Publisher role. The body carries `code: insufficient_workspace_permissions` and `required_role: publisher`.
- `404`: Resource not found
- `409`: The Idempotency-Key was reused with a different payload
- `429`: Too many requests

---

[API](https://skmtc.dev/poster/apis/posterly-api.md) · [All operations](https://skmtc.dev/poster/apis/posterly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/poster/apis/posterly-api/revisions/60be02a61f95?raw)
