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

# Create post

`POST /v1/posts`

Create and optionally publish a post. Immediate posts (`publishNow: true`) include `platformPostUrl` in the response.
Content is optional when media is attached or all platforms have `customContent`. See each platform's schema for media constraints.

## Idempotency

Two layers of duplicate-protection apply, so safe-to-retry callers (network blips, n8n / Zapier retries, etc.) don't accidentally double-post.

**1. Same-request idempotency (5-minute window).**
Pass an `x-request-id` header to mark a logical request. If a second request arrives with the same `x-request-id` while the first is in-flight (or within ~5 minutes of completion), we return **HTTP 200** with the original post in the `existingPost` field — no new post is created. The official Zernio SDKs auto-generate a unique `x-request-id` per call. If you're using a generic HTTP client (curl, n8n's HTTP node, Zapier, custom code), either:
- Set a unique `x-request-id` per logical call (recommended — UUIDv4 is fine)
- Or simply omit the header — we'll treat each request as new

**Common pitfall**: if your workflow tool uses a single execution-level request ID and reuses it across multiple HTTP nodes (e.g. one ID for the whole run, shared across 6 different platform calls), every call after the first will look like a retry of the first and return its post. Generate a fresh ID per node.

**2. Content-hash dedup (24-hour window).**
Independently, we hash `(platform, accountId, content + media URLs)` and reject duplicates within 24 hours with **HTTP 409**. This catches genuine "same content posted twice to the same account" cases regardless of `x-request-id`. Returns `error`, `accountId`, `platform`, and `existingPostId` so you can find the original. To intentionally re-post identical content within 24h, change something (the caption, the media, the account) — the dedup is keyed on the full content fingerprint.

Order: same-`x-request-id` retries (200) are checked first; if no idempotency match, the content-hash dedup (409) runs.

## Headers

- `x-request-id` string, uuid

## Request body

- object
  - `title` string
  - `content` string — Post caption/text. Optional when media is attached or all platforms have customContent. Required for text-only posts.
  - `mediaItems` MediaItem[]
    - `type` 'image' | 'video' | 'gif' | 'document'
    - `url` string, uri
    - `title` string — Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
    - `altText` string — Accessibility alternative text for an image, applied on every platform that supports it: Instagram (feed images only, not Reels/Stories), Facebook, Threads, X/Twitter (max 1000 chars), LinkedIn, Bluesky, and Pinterest (max 500 chars). Ignored on platforms without alt-text support (TikTok, YouTube, Snapchat, Telegram, Reddit, Google Business, WhatsApp) and on video items where the platform does not accept it. Set once per image; the same value is sent to each selected platform.
    - `filename` string
    - `size` integer — Optional file size in bytes
    - `mimeType` string — Optional MIME type (e.g. image/jpeg, video/mp4)
    - `thumbnail` string, uri — Optional custom thumbnail/cover image URL for videos. Supported for Facebook video posts, Facebook Reels, regular video uploads, and LinkedIn video posts. Max 10MB, JPG/PNG recommended.
    - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy).
    - `tiktokProcessed` boolean — Internal flag indicating the image was resized for TikTok
  - `platforms` object[] — Target platforms and accounts for this post. Required for non-draft posts (returns 400 if empty). Drafts can omit platforms.
    - `platform` string, required
    - `accountId` string, required
    - `customContent` string — Platform-specific text override. When set, this content is used instead of the top-level post content for this platform. Useful for tailoring captions per platform (e.g. keeping tweets under 280 characters).
    - `customMedia` MediaItem[]
      - `type` 'image' | 'video' | 'gif' | 'document'
      - `url` string, uri
      - `title` string — Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
      - `altText` string — Accessibility alternative text for an image, applied on every platform that supports it: Instagram (feed images only, not Reels/Stories), Facebook, Threads, X/Twitter (max 1000 chars), LinkedIn, Bluesky, and Pinterest (max 500 chars). Ignored on platforms without alt-text support (TikTok, YouTube, Snapchat, Telegram, Reddit, Google Business, WhatsApp) and on video items where the platform does not accept it. Set once per image; the same value is sent to each selected platform.
      - `filename` string
      - `size` integer — Optional file size in bytes
      - `mimeType` string — Optional MIME type (e.g. image/jpeg, video/mp4)
      - `thumbnail` string, uri — Optional custom thumbnail/cover image URL for videos. Supported for Facebook video posts, Facebook Reels, regular video uploads, and LinkedIn video posts. Max 10MB, JPG/PNG recommended.
      - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy).
      - `tiktokProcessed` boolean — Internal flag indicating the image was resized for TikTok
    - `scheduledFor` string, date-time — Optional per-platform scheduled time override. When omitted, the top-level scheduledFor is used.
    - `platformSpecificData` union
      - TwitterPlatformData — X (Twitter) geo-restriction applies at the media level. Media in geo-restricted tweets will be hidden for users outside the specified countries; the tweet text itself remains visible globally. Requires media to be attached (ignored for text-only tweets).
        - `replyToTweetId` string — ID of an existing tweet to reply to. The published tweet will appear as a reply in that tweet's thread. For threads, only the first tweet replies to the target; subsequent tweets chain normally. X only permits replying to your own posts or posts you are mentioned in; replying to an arbitrary other account's post is rejected by X.
        - `quoteTweetId` string — ID (or full status URL) of an existing tweet to quote-repost. The published tweet becomes a quote tweet of the target. Mutually exclusive with media and poll. X only permits quoting your own posts or posts you are mentioned in / part of the conversation thread of; quoting an arbitrary other account's post is rejected by X. Billed at the standard create rate ($0.015), unlike pasting a tweet URL into the text which is billed at the URL rate ($0.20). For threads, applies to the first tweet only.
        - `replySettings` 'following' | 'mentionedUsers' | 'subscribers' | 'verified' — Controls who can reply to the tweet. "following" allows only people you follow, "mentionedUsers" allows only mentioned users, "subscribers" allows only subscribers, "verified" allows only verified users. Omit for default (everyone can reply). For threads, applies to the first tweet only. Cannot be combined with replyToTweetId.
        - `threadItems` object[] — Complete sequence of tweets in a thread. The first item becomes the root tweet, subsequent items are chained as replies. When threadItems is provided, the top-level content field is used only for display and search purposes, it is NOT published. You must include your first tweet as threadItems[0].
          - `content` string
          - `mediaItems` MediaItem[]
            - `type` 'image' | 'video' | 'gif' | 'document'
            - `url` string, uri
            - `title` string — Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
            - `altText` string — Accessibility alternative text for an image, applied on every platform that supports it: Instagram (feed images only, not Reels/Stories), Facebook, Threads, X/Twitter (max 1000 chars), LinkedIn, Bluesky, and Pinterest (max 500 chars). Ignored on platforms without alt-text support (TikTok, YouTube, Snapchat, Telegram, Reddit, Google Business, WhatsApp) and on video items where the platform does not accept it. Set once per image; the same value is sent to each selected platform.
            - `filename` string
            - `size` integer — Optional file size in bytes
            - `mimeType` string — Optional MIME type (e.g. image/jpeg, video/mp4)
            - `thumbnail` string, uri — Optional custom thumbnail/cover image URL for videos. Supported for Facebook video posts, Facebook Reels, regular video uploads, and LinkedIn video posts. Max 10MB, JPG/PNG recommended.
            - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy).
            - `tiktokProcessed` boolean — Internal flag indicating the image was resized for TikTok
        - `poll` object — Create a poll with this tweet. Mutually exclusive with media attachments and threads.
          - `options` string[], required — Poll options (2-4 choices, max 25 characters each)
          - `duration_minutes` integer, required — Poll duration in minutes (5 min to 7 days)
        - `longVideo` boolean — Enable long video uploads (over 140 seconds) using amplify_video media category. Requires the connected X account to have an active X Premium subscription. When true, videos are uploaded with the amplify_video category which supports longer durations (up to 10 minutes via API). When false or omitted, the standard tweet_video category is used (140 second limit). Note that not all Premium accounts have API long-video access, as X may require separate allowlisting.
        - `geoRestriction` GeoRestriction — Country-level geo-restriction (allowlist). When set, the post is only visible to users in the specified countries. Supported on Facebook (feed posts, videos, reels), X/Twitter (media-level restriction), and LinkedIn (organization pages only, min 300 targeted followers). Ignored on unsupported platforms. Stories (Facebook, Instagram) do not support geo-restriction.
          - `countries` string[], required — ISO 3166-1 alpha-2 country codes (uppercase). Only users in these countries can see the post. Maximum 25 countries per post. Example: ["US", "CA", "GB", "ES"].
        - `paidPartnership` boolean — When true, the post is labeled by X as a paid partnership / paid promotion. For threads, applies to the root tweet only. Field availability may depend on your X API access tier.
        - `madeWithAi` boolean — When true, the post is labeled by X as containing AI-generated media. Per X, this label is for AI-generated media, not AI-written text. For threads, applies to the root tweet only.
        - `sensitiveMedia` object — Marks attached media with a sensitive-content warning. Applied to every media item in the post (requires media; ignored for text-only tweets). At least one flag must be true for the warning to be sent.
          - `adultContent` boolean — Content contains adult material.
          - `graphicViolence` boolean — Content depicts graphic violence.
          - `other` boolean — Content has other sensitive characteristics.
      - ThreadsPlatformData — Up to 10 images per carousel (no videos). Videos must be H.264/AAC MP4, max 5 min. Images JPEG/PNG, max 8 MB. Use threadItems for reply chains.
        - `topic_tag` string — Topic tag for post categorization and discoverability on Threads. Must be 1-50 characters, cannot contain periods (.) or ampersands (&). Overrides auto-extraction from content hashtags when provided.
        - `threadItems` object[] — Complete sequence of posts in a Threads thread. The first item becomes the root post, subsequent items are chained as replies. When threadItems is provided, the top-level content field is used only for display and search purposes, it is NOT published. You must include your first post as threadItems[0].
          - `content` string
          - `mediaItems` MediaItem[]
            - `type` 'image' | 'video' | 'gif' | 'document'
            - `url` string, uri
            - `title` string — Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
            - `altText` string — Accessibility alternative text for an image, applied on every platform that supports it: Instagram (feed images only, not Reels/Stories), Facebook, Threads, X/Twitter (max 1000 chars), LinkedIn, Bluesky, and Pinterest (max 500 chars). Ignored on platforms without alt-text support (TikTok, YouTube, Snapchat, Telegram, Reddit, Google Business, WhatsApp) and on video items where the platform does not accept it. Set once per image; the same value is sent to each selected platform.
            - `filename` string
            - `size` integer — Optional file size in bytes
            - `mimeType` string — Optional MIME type (e.g. image/jpeg, video/mp4)
            - `thumbnail` string, uri — Optional custom thumbnail/cover image URL for videos. Supported for Facebook video posts, Facebook Reels, regular video uploads, and LinkedIn video posts. Max 10MB, JPG/PNG recommended.
            - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy).
            - `tiktokProcessed` boolean — Internal flag indicating the image was resized for TikTok
      - FacebookPlatformData — Feed posts support up to 10 images (no mixed video+image). Stories require single media (24h, no captions). Reels require single vertical video (9:16, 3-60s). Geo-restriction is a hard visibility restriction: users outside the specified countries cannot see the post. Not supported for stories. Draft, carousel, and colored-background text options live under facebookSettings, see FacebookSettings.
        - `contentType` 'story' | 'reel' — Set to 'story' for Page Stories (24h ephemeral) or 'reel' for Reels (short vertical video). Defaults to feed post if omitted.
        - `title` string — Reel title (only for contentType=reel). Separate from the caption/content field.
        - `firstComment` string — Optional first comment to post immediately after publishing (feed posts and reels, not stories). Skipped when facebookSettings.draft is true.
        - `pageId` string — Target Facebook Page ID for multi-page posting. If omitted, uses the default page. Use GET /v1/accounts/{id}/facebook-page to list pages.
        - `geoRestriction` GeoRestriction — Country-level geo-restriction (allowlist). When set, the post is only visible to users in the specified countries. Supported on Facebook (feed posts, videos, reels), X/Twitter (media-level restriction), and LinkedIn (organization pages only, min 300 targeted followers). Ignored on unsupported platforms. Stories (Facebook, Instagram) do not support geo-restriction.
          - `countries` string[], required — ISO 3166-1 alpha-2 country codes (uppercase). Only users in these countries can see the post. Maximum 25 countries per post. Example: ["US", "CA", "GB", "ES"].
        - `facebookSettings` FacebookSettings — Facebook options that must be nested under platformSpecificData.facebookSettings, or sent at the request root as facebookSettings. The remaining Facebook options sit directly on platformSpecificData, see FacebookPlatformData.
          - `draft` boolean — When true, creates the post as a draft in Facebook Publishing Tools instead of publishing immediately. Supported for feed posts (text, link, image, video) and reels. Not supported for stories. Drafts expire after ~30 days.
          - `carouselCards` object[] — Renders the post as a multi-link carousel (organic Page post). When set, mediaItems must be provided with the same length and all items must be images (no videos). Each cards[i] adds the click-through link and headline for the image at mediaItems[i]. Mutually exclusive with contentType=story|reel. Facebook display truncates name at ~35 chars and description at ~30 chars; longer strings are accepted but get truncated on render.
            - `link` string, uri, required — Per-card click destination (required).
            - `name` string — Per-card headline (optional, ~35-char display).
            - `description` string — Per-card subhead (optional, ~30-char display).
          - `carouselLink` string, uri — Optional top-level "See more" destination shown on the carousel end card. Defaults to the first card's link when omitted. Only used together with carouselCards.
          - `textFormatPresetId` string — Facebook-defined preset ID that renders the post as large text on a colored background (Graph `text_format_preset_id`). Supply the raw numeric ID from Meta; we do not publish a catalog of presets and Facebook may change the available set. Pages only (ignored on personal profiles and groups) and text-only feed posts only: the request is rejected with 400 when mediaItems or carouselCards are present, when contentType is story or reel, or when content is empty. An attachment makes Facebook drop the background silently, so those are rejected up front. Length is NOT rejected: Facebook's composer stops offering a background at around 130 characters, but Meta documents no API limit, so longer content publishes and returns a warning instead. A URL detected in the content is NOT attached as a link preview while a preset is set, because a link attachment also makes Facebook drop the background.
      - InstagramPlatformData — Feed aspect ratio 0.8-1.91, carousels up to 10 items, stories require media (no captions). User tag coordinates 0.0-1.0 from top-left. Images over 8 MB and videos over platform limits are auto-compressed.
        - `contentType` 'story' — Set to 'story' to publish as a Story. Default posts become Reels or feed depending on media.
        - `shareToFeed` boolean — For Reels only. When true (default), the Reel appears on both the Reels tab and your main profile feed. Set to false to post to the Reels tab only.
        - `collaborators` string[] — Up to 3 Instagram usernames to invite as collaborators (feed/Reels only)
        - `firstComment` string — Optional first comment to add after the post is created (not applied to Stories)
        - `trialParams` object — Trial Reels configuration. Trial reels are shared to non-followers first and can later be graduated to regular reels manually or automatically based on performance. Only applies to Reels.
          - `graduationStrategy` 'MANUAL' | 'SS_PERFORMANCE' — MANUAL (graduate from Instagram app) or SS_PERFORMANCE (auto-graduate if performs well with non-followers)
        - `userTags` object[] — Tag Instagram users by username. The tag shape depends on the media: photos require x/y coordinates, Reels and videos take username only (coordinates are ignored), stories accept optional coordinates. For carousels, use mediaIndex to target specific slides (defaults to 0); video slides take username-only tags. Photo tags without valid coordinates are skipped.
          - `username` string, required — Instagram username (@ symbol is optional and will be removed automatically)
          - `x` number — X coordinate position from left edge (0.0 = left, 0.5 = center, 1.0 = right). Required for photos, ignored for Reels/videos, optional for stories.
          - `y` number — Y coordinate position from top edge (0.0 = top, 0.5 = center, 1.0 = bottom). Required for photos, ignored for Reels/videos, optional for stories.
          - `mediaIndex` integer — Zero-based index of the carousel item to tag. Defaults to 0. Tags on out-of-range indices are ignored.
        - `audioName` string — Custom name for original audio in Reels. Replaces the default "Original Audio" label. Can only be set once.
        - `thumbOffset` integer — Millisecond offset from video start for the Reel cover frame. Ignored when instagramThumbnail or reelCover is provided. Defaults to 0.
        - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels (JPG or PNG, publicly accessible). Overrides thumbOffset when provided. Also accepted as reelCover (alias).
        - `reelCover` string, uri — Alias for instagramThumbnail. If both are provided, instagramThumbnail takes priority.
        - `isAiGenerated` boolean — When true, the post is labeled by Instagram as containing AI-generated media. Per Meta, this self-disclosure label is for AI-generated media, not AI-written captions. Applies to feed posts, Reels, Stories, and carousels.
      - LinkedInPlatformData — Up to 20 images, no multi-video. Single PDF supported (max 100MB). Link previews auto-generated when no media attached. Use organizationUrn for multi-org posting. Geo-restriction only works for organization pages (not personal profiles) and requires the targeted audience to exceed 300 followers.
        - `documentTitle` string — Title displayed on LinkedIn document (PDF/carousel) posts. Required by LinkedIn for document posts. If omitted, falls back to the media item title, then the filename.
        - `organizationUrn` string — Target LinkedIn Organization URN (e.g. "urn:li:organization:123456789"). If omitted, uses the default org. Use GET /v1/accounts/{id}/linkedin-organizations to list orgs.
        - `firstComment` string — Optional first comment to add after the post is created
        - `disableLinkPreview` boolean — Set to true to disable automatic link previews for URLs in the post content (default is false)
        - `reshareUrl` string — LinkedIn post link to repost (use the post's "Copy link to post" action), or a urn:li:share / urn:li:ugcPost / urn:li:groupPost URN. The published post becomes a quote-reshare: your content is shown as the commentary and the original post is embedded underneath (LinkedIn's "repost with your thoughts"). Mutually exclusive with media. Works on personal profiles and organization pages.
        - `geoRestriction` GeoRestriction — Country-level geo-restriction (allowlist). When set, the post is only visible to users in the specified countries. Supported on Facebook (feed posts, videos, reels), X/Twitter (media-level restriction), and LinkedIn (organization pages only, min 300 targeted followers). Ignored on unsupported platforms. Stories (Facebook, Instagram) do not support geo-restriction.
          - `countries` string[], required — ISO 3166-1 alpha-2 country codes (uppercase). Only users in these countries can see the post. Maximum 25 countries per post. Example: ["US", "CA", "GB", "ES"].
      - PinterestPlatformData
        - `title` string — Pin title. Defaults to first line of content or "Pin". Must be ≤ 100 characters.
        - `boardId` string — Target Pinterest board ID. If omitted, the first available board is used.
        - `link` string, uri — Destination link (pin URL)
        - `coverImageUrl` string, uri — Optional cover image for video pins
        - `coverImageKeyFrameTime` integer — Optional key frame time in seconds for derived video cover
      - YouTubePlatformData — Videos under 3 min auto-detected as Shorts. Custom thumbnails for regular videos only. Scheduled videos are uploaded immediately with the specified visibility.
        - `title` string — Video title. Defaults to first line of content or "Untitled Video". Must be ≤ 100 characters.
        - `visibility` 'public' | 'private' | 'unlisted' — Video visibility: public (default, anyone can watch), unlisted (link only), private (invite only)
        - `madeForKids` boolean — COPPA compliance flag. Set true for child-directed content (restricts comments, notifications, ad targeting). Defaults to false. YouTube may block views if not explicitly set.
        - `firstComment` string — Optional first comment to post immediately after video upload. Up to 10,000 characters (YouTube's comment limit).
        - `containsSyntheticMedia` boolean — AI-generated content disclosure. Set true if the video contains synthetic content that could be mistaken for real. YouTube may add a label.
        - `categoryId` string — YouTube video category ID. Defaults to 22 (People & Blogs). Common: 1 (Film), 2 (Autos), 10 (Music), 15 (Pets), 17 (Sports), 20 (Gaming), 23 (Comedy), 24 (Entertainment), 25 (News), 26 (Howto), 27 (Education), 28 (Science & Tech).
        - `playlistId` string — Optional YouTube playlist ID to add the video to after upload (e.g. 'PLxxxxxxxxxxxxx'). Use GET /v1/accounts/{id}/youtube-playlists to list available playlists. Works for both immediate and scheduled uploads. Quota cost: 50 YouTube API units per call.
      - GoogleBusinessPlatformData — Text and single image only (no videos). Supports STANDARD, EVENT, OFFER, and ALERT post types. Posts appear on GBP, Google Search, and Maps. Use locationId for multi-location posting. Schedule dates accept both ISO 8601 strings (e.g. '2026-04-15T09:00:00Z') and Google's native {year, month, day} objects.
        - `locationId` string — Target GBP location ID (e.g. "locations/123456789"). If omitted, uses the default location. Use GET /v1/accounts/{id}/gmb-locations to list locations.
        - `languageCode` string — BCP 47 language code (e.g. "en", "de", "es"). Auto-detected if omitted. Set explicitly for short or mixed-language posts.
        - `topicType` 'STANDARD' | 'EVENT' | 'OFFER' — Post type. STANDARD is a regular update. EVENT requires the event object. OFFER requires the offer object. Defaults to STANDARD if omitted.
        - `callToAction` object — Optional call-to-action button displayed on the post
          - `type` 'LEARN_MORE' | 'BOOK' | 'ORDER' | 'SHOP' | 'SIGN_UP' | 'CALL', required — Button action type: LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL
          - `url` string, uri, required — Destination URL for the CTA button (required when callToAction is provided)
        - `event` object — Event details. Required when topicType is EVENT. Google returns 400 if omitted for EVENT posts.
          - `title` string, required — Event name (displayed as the event heading on Google Search and Maps)
          - `schedule` object, required — Event date/time range. Uses Google's date format (NOT ISO 8601).
            - `startDate` object, required — Event start date as { year, month, day }
              - …
            - `startTime` object — Optional start time as { hours, minutes } in 24h format
              - …
            - `endDate` object, required — Event end date as { year, month, day }
              - …
            - `endTime` object — Optional end time as { hours, minutes } in 24h format
              - …
        - `offer` object — Offer details. Required when topicType is OFFER. All fields are optional per Google's API, but at least one is recommended.
          - `redeemOnlineUrl` string, uri — URL where the offer can be redeemed online
          - `termsConditions` string — Terms and conditions for the offer
          - `couponCode` string — Coupon code for the offer
      - TikTokPlatformData — Photo carousels up to 35 images. Video titles up to 2200 chars, photo titles truncated to 90 chars. privacyLevel must match creator_info options. Both camelCase and snake_case accepted. Creator Inbox (draft mode): Set draft: true to send content to the TikTok Creator Inbox instead of publishing immediately. The creator receives an inbox notification and completes the post using TikTok's editing flow. This maps to TikTok's post_mode: "MEDIA_UPLOAD" internally. Important: The field publish_type is NOT supported. Use draft: true for Creator Inbox flow. Photo drafts use the /v2/post/publish/content/init/ endpoint with post_mode: "MEDIA_UPLOAD". Video drafts use the dedicated /v2/post/publish/inbox/video/init/ endpoint. When draft: true, the video.upload scope is required. When draft is false or omitted (direct post), the video.publish scope is required. For Creator Inbox, TikTok app version must be 31.8 or higher.
        - `draft` boolean — When true, sends the post to the TikTok Creator Inbox as a draft instead of publishing immediately. The creator receives an inbox notification to complete posting via TikTok's editing flow. Maps to TikTok API post_mode: "MEDIA_UPLOAD" (photos) or the dedicated inbox endpoint (videos). When false or omitted, publishes directly via post_mode: "DIRECT_POST". Note: publish_type is not a supported field. Use this field instead.
        - `privacyLevel` string — One of the values returned by the TikTok creator info API for the account
        - `allowComment` boolean — Allow comments on the post
        - `allowDuet` boolean — Allow duets (required for video posts)
        - `allowStitch` boolean — Allow stitches (required for video posts)
        - `commercialContentType` 'none' | 'brand_organic' | 'brand_content' — Type of commercial content disclosure. Sufficient on its own: "brand_organic" ("Your Brand") implies isBrandOrganicPost and "brand_content" ("Branded Content", paid partnership) implies brandPartnerPromote, so you don't need to send the boolean flags separately. Branded content cannot be posted with privacyLevel SELF_ONLY.
        - `brandPartnerPromote` boolean — Whether the post promotes a brand partner (branded content / paid partnership). Only needed to disclose BOTH types at once (set it alongside commercialContentType "brand_organic"), or to override the value implied by commercialContentType.
        - `isBrandOrganicPost` boolean — Whether the post promotes the creator's own brand (brand organic). Only needed to disclose BOTH types at once (set it alongside commercialContentType "brand_content"), or to override the value implied by commercialContentType.
        - `contentPreviewConfirmed` boolean — User has confirmed they previewed the content
        - `expressConsentGiven` boolean — User has given express consent for posting
        - `mediaType` 'video' | 'photo' — Optional override. Defaults based on provided media items.
        - `videoCoverTimestampMs` integer — Optional for video posts. Timestamp in milliseconds to select which frame to use as thumbnail (defaults to 1000ms/1 second). Ignored when videoCoverImageUrl is provided.
        - `videoCoverImageUrl` string, uri — Optional for video posts. URL of a custom thumbnail image (JPG, PNG, or WebP, max 20MB). The image is stitched as a single frame at the start of the video and used as the cover. Overrides videoCoverTimestampMs when provided.
        - `photoCoverIndex` integer — Optional for photo carousels. Index of image to use as cover, 0-based (defaults to 0/first image).
        - `autoAddMusic` boolean — When true, TikTok may add recommended music (photos only)
        - `videoMadeWithAi` boolean — Set true to disclose AI-generated content
        - `description` string — Optional long-form description for photo posts (max 4000 chars). Recommended when content exceeds 90 chars, as photo titles are auto-truncated.
      - TelegramPlatformData — Text, images (up to 10), videos (up to 10), and mixed media albums. Captions up to 1024 chars for media, 4096 for text-only.
        - `parseMode` 'HTML' | 'Markdown' | 'MarkdownV2' — Text formatting mode for the message (default is HTML)
        - `disableWebPagePreview` boolean — Disable link preview generation for URLs in the message
        - `disableNotification` boolean — Send the message silently (users will receive notification without sound)
        - `protectContent` boolean — Protect message content from forwarding and saving
      - SnapchatPlatformData — Requires a Public Profile. Single media item only. Content types: story (ephemeral 24h), saved_story (permanent, title max 45 chars), spotlight (video, max 160 chars).
        - `contentType` 'story' | 'saved_story' | 'spotlight' — Content type: story (ephemeral 24h, default), saved_story (permanent on Public Profile), spotlight (video feed)
      - RedditPlatformData — Posts are either link (with URL/media), native video (via nativeVideo), or self (text-only). Use forceSelf to override. Subreddit defaults to the account's configured one. Some subreddits require a flair.
        - `subreddit` string — Target subreddit name (without "r/" prefix). Overrides the default. Use GET /v1/accounts/{id}/reddit-subreddits to list options.
        - `title` string — Post title. Defaults to the first line of content, truncated to 300 characters.
        - `url` string, uri — URL for link posts. If provided (and forceSelf is not true), creates a link post instead of a text post.
        - `forceSelf` boolean — When true, creates a text/self post even when a URL or media is provided.
        - `flairId` string — Flair ID for the post. Required by some subreddits. Use GET /v1/accounts/{id}/reddit-flairs?subreddit=name to list flairs.
        - `flairText` string — Custom flair text, for subreddits that allow free-text flair. Ignored when flairId is provided (flairId wins).
        - `nsfw` boolean — Mark the post as NSFW (Not Safe For Work / over 18).
        - `spoiler` boolean — Mark the post as a spoiler. The subreddit must have spoiler tagging enabled for this to take effect.
        - `sendreplies` boolean — Whether to receive inbox replies for comments on this post. Set to false to opt out.
        - `nativeVideo` boolean — Controls Reddit's native video upload flow. When true (default for video mediaItems), the video is uploaded to Reddit's CDN and submitted with kind=video so it renders as an embedded Reddit video player. Reddit transcodes server-side (1080p/30fps cap). Set to false to fall back to a legacy link post. If the subreddit blocks video posts, the upload falls back to a link post automatically.
        - `videogif` boolean — When true (and nativeVideo is active), submits the video as a silent videogif (kind=videogif). Use for short looping clips without audio.
        - `videoPosterUrl` string, uri — Optional poster/thumbnail image URL for native video posts. If omitted, the first frame of the video is extracted and used automatically.
      - BlueskyPlatformData — Bluesky post settings. Supports text posts with up to 4 images or a single video. threadItems creates a reply chain (Bluesky thread). Images exceeding 1MB are automatically compressed. Alt text supported via mediaItem properties.
        - `threadItems` object[] — Complete sequence of posts in a Bluesky thread. The first item becomes the root post, subsequent items are chained as replies. When threadItems is provided, the top-level content field is used only for display and search purposes, it is NOT published. You must include your first post as threadItems[0].
          - `content` string
          - `mediaItems` MediaItem[]
            - `type` 'image' | 'video' | 'gif' | 'document'
            - `url` string, uri
            - `title` string — Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
            - `altText` string — Accessibility alternative text for an image, applied on every platform that supports it: Instagram (feed images only, not Reels/Stories), Facebook, Threads, X/Twitter (max 1000 chars), LinkedIn, Bluesky, and Pinterest (max 500 chars). Ignored on platforms without alt-text support (TikTok, YouTube, Snapchat, Telegram, Reddit, Google Business, WhatsApp) and on video items where the platform does not accept it. Set once per image; the same value is sent to each selected platform.
            - `filename` string
            - `size` integer — Optional file size in bytes
            - `mimeType` string — Optional MIME type (e.g. image/jpeg, video/mp4)
            - `thumbnail` string, uri — Optional custom thumbnail/cover image URL for videos. Supported for Facebook video posts, Facebook Reels, regular video uploads, and LinkedIn video posts. Max 10MB, JPG/PNG recommended.
            - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy).
            - `tiktokProcessed` boolean — Internal flag indicating the image was resized for TikTok
      - DiscordPlatformData — Discord message settings. Supports plain text (2,000 chars), rich embeds (up to 10), native polls, forum posts, threads, and announcement crossposts. Media attachments support images (JPEG, PNG, GIF, WebP), videos (MP4), and documents (up to 10 files, 25 MB each). Webhook identity (username + avatar) can be customized per-account via PATCH /v1/connect/discord or per-post via webhookUsername/webhookAvatarUrl.
        - `channelId` string, required — Target channel snowflake ID. Determines which channel in the connected server receives the message.
        - `embeds` object[] — Up to 10 Discord embed objects (combined max 6,000 characters across all embeds). Sent alongside or instead of plain-text content.
          - `title` string — Embed title (max 256 chars)
          - `description` string — Embed body text (max 4,096 chars)
          - `url` string — URL the title links to
          - `color` integer — Embed accent color as decimal integer (e.g. 5814783 for blue). Convert hex to decimal.
          - `image` object
            - `url` string
          - `thumbnail` object
            - `url` string
          - `footer` object
            - `text` string — Footer text (max 2,048 chars)
            - `icon_url` string
          - `author` object
            - `name` string — Author name (max 256 chars)
            - `url` string
            - `icon_url` string
          - `fields` object[] — Up to 25 fields per embed
            - `name` string, required — Field name (max 256 chars)
            - `value` string, required — Field value (max 1,024 chars)
            - `inline` boolean — Display fields side-by-side
        - `poll` object — Native Discord poll. Cannot be combined with media attachments in the same message.
          - `question` object
            - `text` string, required — Poll question (max 300 chars)
          - `answers` object[] — 1-10 answer options
            - `poll_media` object
              - …
          - `duration` integer — Poll duration in hours (1-768). Default 24.
          - `allow_multiselect` boolean — Allow users to select multiple answers. Default false.
        - `crosspost` boolean — Auto-crosspost to every server following this announcement channel (type 5). No-op for regular text channels.
        - `forumThreadName` string — Thread title for forum channel posts (type 15). Required when posting to a forum channel.
        - `forumAppliedTags` string[] — Tag snowflake IDs to apply to forum posts. Max 5 tags.
        - `threadFromMessage` object — Create a follow-up thread under the published message.
          - `name` string — Thread name (1-100 chars)
          - `autoArchiveDuration` 60 | 1440 | 4320 | 10080 — Auto-archive after inactivity (minutes)
          - `rateLimitPerUser` integer — Slow-mode duration in seconds (0-21600)
        - `tts` boolean — Send as text-to-speech message. Discord reads the message aloud in the channel.
        - `webhookUsername` string — Override the webhook display name for this post only (1-80 chars). Falls back to the account-level default set via PATCH /v1/connect/discord.
        - `webhookAvatarUrl` string — Override the webhook avatar URL for this post only. Falls back to the account-level default.
      - SlackPlatformData — Slack message settings. Posts mrkdwn text (up to 40,000 chars; Slack truncates beyond that) to the channel fixed by the connected account, with up to 10 media files per post uploaded via Slack's file API (the text becomes the caption). The target channel is chosen at connect time — one connected account per channel — so channelId is NOT accepted here (a 400 is returned); connect the desired channel via /v1/connect/slack and target its accountId. Messages over 4,000 characters cannot be edited later (Slack's edit limit is stricter than its post limit).
        - `threadTs` string — Parent message ts to post this message as a thread reply (e.g. "1503435956.000247").
        - `unfurlLinks` boolean — Expand links in the message into preview cards. Default true.
        - `unfurlMedia` boolean — Expand media links into inline previews. Default true.
        - `username` string — Override the bot display name for this message only (requires no setup; shown with an APP badge). Does not change the app identity in the sidebar.
        - `iconUrl` string — Override the bot avatar image URL for this message only.
  - `scheduledFor` string, date-time
  - `publishNow` boolean
  - `isDraft` boolean — When true, saves the post as a draft. When none of scheduledFor, publishNow, or queuedFromProfile are provided, the post defaults to draft automatically.
  - `timezone` string
  - `tags` string[] — Tags/keywords. YouTube constraints: each tag max 100 chars, combined max 500 chars, duplicates auto-removed.
  - `hashtags` string[]
  - `mentions` string[] — Stored for reference only. This field does NOT automatically create @mentions when publishing. For LinkedIn @mentions, use the /v1/accounts/{accountId}/linkedin-mentions endpoint to resolve profile URLs to URNs, then embed the returned mentionFormat directly in the post content field.
  - `crosspostingEnabled` boolean
  - `metadata` object
  - `tiktokSettings` TikTokPlatformData — Photo carousels up to 35 images. Video titles up to 2200 chars, photo titles truncated to 90 chars. privacyLevel must match creator_info options. Both camelCase and snake_case accepted. Creator Inbox (draft mode): Set draft: true to send content to the TikTok Creator Inbox instead of publishing immediately. The creator receives an inbox notification and completes the post using TikTok's editing flow. This maps to TikTok's post_mode: "MEDIA_UPLOAD" internally. Important: The field publish_type is NOT supported. Use draft: true for Creator Inbox flow. Photo drafts use the /v2/post/publish/content/init/ endpoint with post_mode: "MEDIA_UPLOAD". Video drafts use the dedicated /v2/post/publish/inbox/video/init/ endpoint. When draft: true, the video.upload scope is required. When draft is false or omitted (direct post), the video.publish scope is required. For Creator Inbox, TikTok app version must be 31.8 or higher.
    - `draft` boolean — When true, sends the post to the TikTok Creator Inbox as a draft instead of publishing immediately. The creator receives an inbox notification to complete posting via TikTok's editing flow. Maps to TikTok API post_mode: "MEDIA_UPLOAD" (photos) or the dedicated inbox endpoint (videos). When false or omitted, publishes directly via post_mode: "DIRECT_POST". Note: publish_type is not a supported field. Use this field instead.
    - `privacyLevel` string — One of the values returned by the TikTok creator info API for the account
    - `allowComment` boolean — Allow comments on the post
    - `allowDuet` boolean — Allow duets (required for video posts)
    - `allowStitch` boolean — Allow stitches (required for video posts)
    - `commercialContentType` 'none' | 'brand_organic' | 'brand_content' — Type of commercial content disclosure. Sufficient on its own: "brand_organic" ("Your Brand") implies isBrandOrganicPost and "brand_content" ("Branded Content", paid partnership) implies brandPartnerPromote, so you don't need to send the boolean flags separately. Branded content cannot be posted with privacyLevel SELF_ONLY.
    - `brandPartnerPromote` boolean — Whether the post promotes a brand partner (branded content / paid partnership). Only needed to disclose BOTH types at once (set it alongside commercialContentType "brand_organic"), or to override the value implied by commercialContentType.
    - `isBrandOrganicPost` boolean — Whether the post promotes the creator's own brand (brand organic). Only needed to disclose BOTH types at once (set it alongside commercialContentType "brand_content"), or to override the value implied by commercialContentType.
    - `contentPreviewConfirmed` boolean — User has confirmed they previewed the content
    - `expressConsentGiven` boolean — User has given express consent for posting
    - `mediaType` 'video' | 'photo' — Optional override. Defaults based on provided media items.
    - `videoCoverTimestampMs` integer — Optional for video posts. Timestamp in milliseconds to select which frame to use as thumbnail (defaults to 1000ms/1 second). Ignored when videoCoverImageUrl is provided.
    - `videoCoverImageUrl` string, uri — Optional for video posts. URL of a custom thumbnail image (JPG, PNG, or WebP, max 20MB). The image is stitched as a single frame at the start of the video and used as the cover. Overrides videoCoverTimestampMs when provided.
    - `photoCoverIndex` integer — Optional for photo carousels. Index of image to use as cover, 0-based (defaults to 0/first image).
    - `autoAddMusic` boolean — When true, TikTok may add recommended music (photos only)
    - `videoMadeWithAi` boolean — Set true to disclose AI-generated content
    - `description` string — Optional long-form description for photo posts (max 4000 chars). Recommended when content exceeds 90 chars, as photo titles are auto-truncated.
  - `facebookSettings` FacebookSettings — Facebook options that must be nested under platformSpecificData.facebookSettings, or sent at the request root as facebookSettings. The remaining Facebook options sit directly on platformSpecificData, see FacebookPlatformData.
    - `draft` boolean — When true, creates the post as a draft in Facebook Publishing Tools instead of publishing immediately. Supported for feed posts (text, link, image, video) and reels. Not supported for stories. Drafts expire after ~30 days.
    - `carouselCards` object[] — Renders the post as a multi-link carousel (organic Page post). When set, mediaItems must be provided with the same length and all items must be images (no videos). Each cards[i] adds the click-through link and headline for the image at mediaItems[i]. Mutually exclusive with contentType=story|reel. Facebook display truncates name at ~35 chars and description at ~30 chars; longer strings are accepted but get truncated on render.
      - `link` string, uri, required — Per-card click destination (required).
      - `name` string — Per-card headline (optional, ~35-char display).
      - `description` string — Per-card subhead (optional, ~30-char display).
    - `carouselLink` string, uri — Optional top-level "See more" destination shown on the carousel end card. Defaults to the first card's link when omitted. Only used together with carouselCards.
    - `textFormatPresetId` string — Facebook-defined preset ID that renders the post as large text on a colored background (Graph `text_format_preset_id`). Supply the raw numeric ID from Meta; we do not publish a catalog of presets and Facebook may change the available set. Pages only (ignored on personal profiles and groups) and text-only feed posts only: the request is rejected with 400 when mediaItems or carouselCards are present, when contentType is story or reel, or when content is empty. An attachment makes Facebook drop the background silently, so those are rejected up front. Length is NOT rejected: Facebook's composer stops offering a background at around 130 characters, but Meta documents no API limit, so longer content publishes and returns a warning instead. A URL detected in the content is NOT attached as a link preview while a preset is set, because a link attachment also makes Facebook drop the background.
  - `recycling` RecyclingConfig — Configure automatic post recycling (reposting at regular intervals). After the post is published, the system creates new scheduled copies at the specified interval until expiration conditions are met. Supports weekly or monthly intervals. Maximum 10 active recycling posts per account. YouTube and TikTok platforms are excluded from recycling. Content variations are recommended for Twitter and Pinterest to avoid duplicate flags.
    - `enabled` boolean — Set to false to disable recycling on this post
    - `gap` integer — Number of interval units between each repost. Required when enabling recycling.
    - `gapFreq` 'week' | 'month' — Interval unit for the gap. Defaults to 'month'.
    - `startDate` string, date-time — When to start the recycling cycle. Defaults to the post's scheduledFor date.
    - `expireCount` integer, nullable — Stop recycling after this many copies have been created. Send null on update to clear this limit.
    - `expireDate` string, date-time, nullable — Stop recycling after this date, regardless of count. Send null on update to clear this limit.
    - `contentVariations` string[] — Array of content variations for recycled copies. On each recycle, the next variation is used in round-robin order. Recommended for Twitter and Pinterest to avoid duplicate content flags. If omitted, the original post content is used for all recycled copies. Send an empty array [] to clear existing variations. Must have 2+ entries when setting variations. Platform-level customContent still overrides the base content per platform.
  - `queuedFromProfile` string — Profile ID to schedule via queue. When provided without scheduledFor, the post is auto-assigned to the next available slot. Do not call /v1/queue/next-slot and use that time in scheduledFor, as that bypasses queue locking.
  - `queueId` string — Specific queue ID to use when scheduling via queue. Only used when queuedFromProfile is also provided. If omitted, uses the profile's default queue.

## Response `201`

Post created

- PostCreateResponse
  - `message` string
  - `post` Post
    - `_id` string
    - `userId` union
      - string
      - User
        - `_id` string
        - `email` string
        - `name` string
        - `role` string
        - `createdAt` string, date-time
    - `title` string — YouTube: title must be ≤ 100 characters.
    - `content` string
    - `mediaItems` MediaItem[]
      - `type` 'image' | 'video' | 'gif' | 'document'
      - `url` string, uri
      - `title` string — Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
      - `altText` string — Accessibility alternative text for an image, applied on every platform that supports it: Instagram (feed images only, not Reels/Stories), Facebook, Threads, X/Twitter (max 1000 chars), LinkedIn, Bluesky, and Pinterest (max 500 chars). Ignored on platforms without alt-text support (TikTok, YouTube, Snapchat, Telegram, Reddit, Google Business, WhatsApp) and on video items where the platform does not accept it. Set once per image; the same value is sent to each selected platform.
      - `filename` string
      - `size` integer — Optional file size in bytes
      - `mimeType` string — Optional MIME type (e.g. image/jpeg, video/mp4)
      - `thumbnail` string, uri — Optional custom thumbnail/cover image URL for videos. Supported for Facebook video posts, Facebook Reels, regular video uploads, and LinkedIn video posts. Max 10MB, JPG/PNG recommended.
      - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy).
      - `tiktokProcessed` boolean — Internal flag indicating the image was resized for TikTok
    - `platforms` PlatformTarget[]
      - `platform` string — Supported values: twitter, threads, instagram, youtube, facebook, linkedin, pinterest, reddit, tiktok, bluesky, googlebusiness, telegram
      - `accountId` union
        - string
        - SocialAccount
          - `_id` string, required
          - `platform` 'tiktok' | 'instagram' | 'facebook' | 'youtube' | 'linkedin' | 'twitter' | 'threads' | 'pinterest' | 'reddit' | 'bluesky' | 'googlebusiness' | 'telegram' | 'snapchat' | 'discord' | 'slack' | 'whatsapp' | 'linkedinads' | 'metaads' | 'pinterestads' | 'tiktokads' | 'xads' | 'googleads' | 'openaiads', required
          - `profileId` union, required
            - string
            - Profile
              - …
          - `username` string
          - `displayName` string
          - `profilePicture` string, nullable — URL to the account's profile picture on the platform. May be null if the platform does not provide one.
          - `profileUrl` string — Full profile URL for the connected account on its platform.
          - `isActive` boolean, required
          - `needsReconnection` boolean — The platform definitively reported the stored OAuth token as dead. While true, GET /v1/connect/{platform}/ads returns a fresh authUrl (implicit force=true) instead of alreadyConnected, so re-running the connect flow recovers the account. Cleared automatically when the account is re-authorized.
          - `followersCount` number — Follower count (only included if user has analytics add-on)
          - `followersLastUpdated` string, date-time — Last time follower count was updated (only included if user has analytics add-on)
          - `parentAccountId` string, nullable — Reference to the parent posting SocialAccount. Set for ads accounts that share or derive from a posting account's OAuth token. null for standalone ads (Google Ads) and all posting accounts.
          - `enabled` boolean — Whether the user explicitly activated this account. false means the account was created as a side effect (e.g., posting account auto-created when user connected ads first). Posting UI and scheduler ignore accounts with enabled: false.
          - `metadata` object — Platform-specific metadata. Fields vary by platform. For WhatsApp accounts, includes: - qualityRating: Phone number quality rating from Meta (GREEN, YELLOW, RED, or UNKNOWN) - nameStatus: Display name review status (APPROVED, PENDING_REVIEW, DECLINED, or NONE). A declined or pending display name does not by itself block sending; sendability is reported separately via health_status (can_send_message). - messagingLimitTier: Maximum unique business-initiated conversations per 24h rolling window (TIER_250, TIER_1K, TIER_10K, TIER_100K, or TIER_UNLIMITED). Scales automatically as quality rating improves. - verifiedName: Meta-verified business display name - displayPhoneNumber: Formatted phone number (e.g., "+1 555-123-4567") - wabaId: WhatsApp Business Account ID - phoneNumberId: Meta phone number ID For LinkedIn accounts, profileData carries the profile details refreshed on each daily snapshot: - profileData.bio: The member's headline for personal accounts, or the organization description for organization accounts. null when the member has not set one. - profileData.extraData.vanityName: The member's profile slug, i.e. the /in/{vanityName} segment of profileUrl. Personal accounts only; an organization's own slug is in metadata.organizationInfo.vanityName.
      - `customContent` string — Platform-specific text override. When set, this content is used instead of the top-level post content for this platform. Useful for tailoring captions per platform (e.g. keeping tweets under 280 characters).
      - `customMedia` MediaItem[]
        - `type` 'image' | 'video' | 'gif' | 'document'
        - `url` string, uri
        - `title` string — Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
        - `altText` string — Accessibility alternative text for an image, applied on every platform that supports it: Instagram (feed images only, not Reels/Stories), Facebook, Threads, X/Twitter (max 1000 chars), LinkedIn, Bluesky, and Pinterest (max 500 chars). Ignored on platforms without alt-text support (TikTok, YouTube, Snapchat, Telegram, Reddit, Google Business, WhatsApp) and on video items where the platform does not accept it. Set once per image; the same value is sent to each selected platform.
        - `filename` string
        - `size` integer — Optional file size in bytes
        - `mimeType` string — Optional MIME type (e.g. image/jpeg, video/mp4)
        - `thumbnail` string, uri — Optional custom thumbnail/cover image URL for videos. Supported for Facebook video posts, Facebook Reels, regular video uploads, and LinkedIn video posts. Max 10MB, JPG/PNG recommended.
        - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy).
        - `tiktokProcessed` boolean — Internal flag indicating the image was resized for TikTok
      - `scheduledFor` string, date-time — Optional per-platform scheduled time override (uses post.scheduledFor when omitted)
      - `platformSpecificData` union — Platform-specific overrides and options.
        - object — X (Twitter) geo-restriction applies at the media level. Media in geo-restricted tweets will be hidden for users outside the specified countries; the tweet text itself remains visible globally. Requires media to be attached (ignored for text-only tweets).
          - `replyToTweetId` string — ID of an existing tweet to reply to. The published tweet will appear as a reply in that tweet's thread. For threads, only the first tweet replies to the target; subsequent tweets chain normally. X only permits replying to your own posts or posts you are mentioned in; replying to an arbitrary other account's post is rejected by X.
          - `quoteTweetId` string — ID (or full status URL) of an existing tweet to quote-repost. The published tweet becomes a quote tweet of the target. Mutually exclusive with media and poll. X only permits quoting your own posts or posts you are mentioned in / part of the conversation thread of; quoting an arbitrary other account's post is rejected by X. Billed at the standard create rate ($0.015), unlike pasting a tweet URL into the text which is billed at the URL rate ($0.20). For threads, applies to the first tweet only.
          - `replySettings` 'following' | 'mentionedUsers' | 'subscribers' | 'verified' — Controls who can reply to the tweet. "following" allows only people you follow, "mentionedUsers" allows only mentioned users, "subscribers" allows only subscribers, "verified" allows only verified users. Omit for default (everyone can reply). For threads, applies to the first tweet only. Cannot be combined with replyToTweetId.
          - `threadItems` object[] — Complete sequence of tweets in a thread. The first item becomes the root tweet, subsequent items are chained as replies. When threadItems is provided, the top-level content field is used only for display and search purposes, it is NOT published. You must include your first tweet as threadItems[0].
            - `content` string
            - `mediaItems` MediaItem[]
              - …
          - `poll` object — Create a poll with this tweet. Mutually exclusive with media attachments and threads.
            - `options` string[], required — Poll options (2-4 choices, max 25 characters each)
            - `duration_minutes` integer, required — Poll duration in minutes (5 min to 7 days)
          - `longVideo` boolean — Enable long video uploads (over 140 seconds) using amplify_video media category. Requires the connected X account to have an active X Premium subscription. When true, videos are uploaded with the amplify_video category which supports longer durations (up to 10 minutes via API). When false or omitted, the standard tweet_video category is used (140 second limit). Note that not all Premium accounts have API long-video access, as X may require separate allowlisting.
          - `geoRestriction` GeoRestriction — Country-level geo-restriction (allowlist). When set, the post is only visible to users in the specified countries. Supported on Facebook (feed posts, videos, reels), X/Twitter (media-level restriction), and LinkedIn (organization pages only, min 300 targeted followers). Ignored on unsupported platforms. Stories (Facebook, Instagram) do not support geo-restriction.
            - `countries` string[], required — ISO 3166-1 alpha-2 country codes (uppercase). Only users in these countries can see the post. Maximum 25 countries per post. Example: ["US", "CA", "GB", "ES"].
          - `paidPartnership` boolean — When true, the post is labeled by X as a paid partnership / paid promotion. For threads, applies to the root tweet only. Field availability may depend on your X API access tier.
          - `madeWithAi` boolean — When true, the post is labeled by X as containing AI-generated media. Per X, this label is for AI-generated media, not AI-written text. For threads, applies to the root tweet only.
          - `sensitiveMedia` object — Marks attached media with a sensitive-content warning. Applied to every media item in the post (requires media; ignored for text-only tweets). At least one flag must be true for the warning to be sent.
            - `adultContent` boolean — Content contains adult material.
            - `graphicViolence` boolean — Content depicts graphic violence.
            - `other` boolean — Content has other sensitive characteristics.
        - object — Up to 10 images per carousel (no videos). Videos must be H.264/AAC MP4, max 5 min. Images JPEG/PNG, max 8 MB. Use threadItems for reply chains.
          - `topic_tag` string — Topic tag for post categorization and discoverability on Threads. Must be 1-50 characters, cannot contain periods (.) or ampersands (&). Overrides auto-extraction from content hashtags when provided.
          - `threadItems` object[] — Complete sequence of posts in a Threads thread. The first item becomes the root post, subsequent items are chained as replies. When threadItems is provided, the top-level content field is used only for display and search purposes, it is NOT published. You must include your first post as threadItems[0].
            - `content` string
            - `mediaItems` MediaItem[]
              - …
        - object — Feed posts support up to 10 images (no mixed video+image). Stories require single media (24h, no captions). Reels require single vertical video (9:16, 3-60s). Geo-restriction is a hard visibility restriction: users outside the specified countries cannot see the post. Not supported for stories. Draft, carousel, and colored-background text options live under facebookSettings, see FacebookSettings.
          - `contentType` 'story' | 'reel' — Set to 'story' for Page Stories (24h ephemeral) or 'reel' for Reels (short vertical video). Defaults to feed post if omitted.
          - `title` string — Reel title (only for contentType=reel). Separate from the caption/content field.
          - `firstComment` string — Optional first comment to post immediately after publishing (feed posts and reels, not stories). Skipped when facebookSettings.draft is true.
          - `pageId` string — Target Facebook Page ID for multi-page posting. If omitted, uses the default page. Use GET /v1/accounts/{id}/facebook-page to list pages.
          - `geoRestriction` GeoRestriction — Country-level geo-restriction (allowlist). When set, the post is only visible to users in the specified countries. Supported on Facebook (feed posts, videos, reels), X/Twitter (media-level restriction), and LinkedIn (organization pages only, min 300 targeted followers). Ignored on unsupported platforms. Stories (Facebook, Instagram) do not support geo-restriction.
            - `countries` string[], required — ISO 3166-1 alpha-2 country codes (uppercase). Only users in these countries can see the post. Maximum 25 countries per post. Example: ["US", "CA", "GB", "ES"].
          - `facebookSettings` FacebookSettings — Facebook options that must be nested under platformSpecificData.facebookSettings, or sent at the request root as facebookSettings. The remaining Facebook options sit directly on platformSpecificData, see FacebookPlatformData.
            - `draft` boolean — When true, creates the post as a draft in Facebook Publishing Tools instead of publishing immediately. Supported for feed posts (text, link, image, video) and reels. Not supported for stories. Drafts expire after ~30 days.
            - `carouselCards` object[] — Renders the post as a multi-link carousel (organic Page post). When set, mediaItems must be provided with the same length and all items must be images (no videos). Each cards[i] adds the click-through link and headline for the image at mediaItems[i]. Mutually exclusive with contentType=story|reel. Facebook display truncates name at ~35 chars and description at ~30 chars; longer strings are accepted but get truncated on render.
              - …
            - `carouselLink` string, uri — Optional top-level "See more" destination shown on the carousel end card. Defaults to the first card's link when omitted. Only used together with carouselCards.
            - `textFormatPresetId` string — Facebook-defined preset ID that renders the post as large text on a colored background (Graph `text_format_preset_id`). Supply the raw numeric ID from Meta; we do not publish a catalog of presets and Facebook may change the available set. Pages only (ignored on personal profiles and groups) and text-only feed posts only: the request is rejected with 400 when mediaItems or carouselCards are present, when contentType is story or reel, or when content is empty. An attachment makes Facebook drop the background silently, so those are rejected up front. Length is NOT rejected: Facebook's composer stops offering a background at around 130 characters, but Meta documents no API limit, so longer content publishes and returns a warning instead. A URL detected in the content is NOT attached as a link preview while a preset is set, because a link attachment also makes Facebook drop the background.
        - object — Feed aspect ratio 0.8-1.91, carousels up to 10 items, stories require media (no captions). User tag coordinates 0.0-1.0 from top-left. Images over 8 MB and videos over platform limits are auto-compressed.
          - `contentType` 'story' — Set to 'story' to publish as a Story. Default posts become Reels or feed depending on media.
          - `shareToFeed` boolean — For Reels only. When true (default), the Reel appears on both the Reels tab and your main profile feed. Set to false to post to the Reels tab only.
          - `collaborators` string[] — Up to 3 Instagram usernames to invite as collaborators (feed/Reels only)
          - `firstComment` string — Optional first comment to add after the post is created (not applied to Stories)
          - `trialParams` object — Trial Reels configuration. Trial reels are shared to non-followers first and can later be graduated to regular reels manually or automatically based on performance. Only applies to Reels.
            - `graduationStrategy` 'MANUAL' | 'SS_PERFORMANCE' — MANUAL (graduate from Instagram app) or SS_PERFORMANCE (auto-graduate if performs well with non-followers)
          - `userTags` object[] — Tag Instagram users by username. The tag shape depends on the media: photos require x/y coordinates, Reels and videos take username only (coordinates are ignored), stories accept optional coordinates. For carousels, use mediaIndex to target specific slides (defaults to 0); video slides take username-only tags. Photo tags without valid coordinates are skipped.
            - `username` string, required — Instagram username (@ symbol is optional and will be removed automatically)
            - `x` number — X coordinate position from left edge (0.0 = left, 0.5 = center, 1.0 = right). Required for photos, ignored for Reels/videos, optional for stories.
            - `y` number — Y coordinate position from top edge (0.0 = top, 0.5 = center, 1.0 = bottom). Required for photos, ignored for Reels/videos, optional for stories.
            - `mediaIndex` integer — Zero-based index of the carousel item to tag. Defaults to 0. Tags on out-of-range indices are ignored.
          - `audioName` string — Custom name for original audio in Reels. Replaces the default "Original Audio" label. Can only be set once.
          - `thumbOffset` integer — Millisecond offset from video start for the Reel cover frame. Ignored when instagramThumbnail or reelCover is provided. Defaults to 0.
          - `instagramThumbnail` string, uri — Custom cover image URL for Instagram Reels (JPG or PNG, publicly accessible). Overrides thumbOffset when provided. Also accepted as reelCover (alias).
          - `reelCover` string, uri — Alias for instagramThumbnail. If both are provided, instagramThumbnail takes priority.
          - `isAiGenerated` boolean — When true, the post is labeled by Instagram as containing AI-generated media. Per Meta, this self-disclosure label is for AI-generated media, not AI-written captions. Applies to feed posts, Reels, Stories, and carousels.
        - object — Up to 20 images, no multi-video. Single PDF supported (max 100MB). Link previews auto-generated when no media attached. Use organizationUrn for multi-org posting. Geo-restriction only works for organization pages (not personal profiles) and requires the targeted audience to exceed 300 followers.
          - `documentTitle` string — Title displayed on LinkedIn document (PDF/carousel) posts. Required by LinkedIn for document posts. If omitted, falls back to the media item title, then the filename.
          - `organizationUrn` string — Target LinkedIn Organization URN (e.g. "urn:li:organization:123456789"). If omitted, uses the default org. Use GET /v1/accounts/{id}/linkedin-organizations to list orgs.
          - `firstComment` string — Optional first comment to add after the post is created
          - `disableLinkPreview` boolean — Set to true to disable automatic link previews for URLs in the post content (default is false)
          - `reshareUrl` string — LinkedIn post link to repost (use the post's "Copy link to post" action), or a urn:li:share / urn:li:ugcPost / urn:li:groupPost URN. The published post becomes a quote-reshare: your content is shown as the commentary and the original post is embedded underneath (LinkedIn's "repost with your thoughts"). Mutually exclusive with media. Works on personal profiles and organization pages.
          - `geoRestriction` GeoRestriction — Country-level geo-restriction (allowlist). When set, the post is only visible to users in the specified countries. Supported on Facebook (feed posts, videos, reels), X/Twitter (media-level restriction), and LinkedIn (organization pages only, min 300 targeted followers). Ignored on unsupported platforms. Stories (Facebook, Instagram) do not support geo-restriction.
            - `countries` string[], required — ISO 3166-1 alpha-2 country codes (uppercase). Only users in these countries can see the post. Maximum 25 countries per post. Example: ["US", "CA", "GB", "ES"].
        - object
          - `title` string — Pin title. Defaults to first line of content or "Pin". Must be ≤ 100 characters.
          - `boardId` string — Target Pinterest board ID. If omitted, the first available board is used.
          - `link` string, uri — Destination link (pin URL)
          - `coverImageUrl` string, uri — Optional cover image for video pins
          - `coverImageKeyFrameTime` integer — Optional key frame time in seconds for derived video cover
        - object — Videos under 3 min auto-detected as Shorts. Custom thumbnails for regular videos only. Scheduled videos are uploaded immediately with the specified visibility.
          - `title` string — Video title. Defaults to first line of content or "Untitled Video". Must be ≤ 100 characters.
          - `visibility` 'public' | 'private' | 'unlisted' — Video visibility: public (default, anyone can watch), unlisted (link only), private (invite only)
          - `madeForKids` boolean — COPPA compliance flag. Set true for child-directed content (restricts comments, notifications, ad targeting). Defaults to false. YouTube may block views if not explicitly set.
          - `firstComment` string — Optional first comment to post immediately after video upload. Up to 10,000 characters (YouTube's comment limit).
          - `containsSyntheticMedia` boolean — AI-generated content disclosure. Set true if the video contains synthetic content that could be mistaken for real. YouTube may add a label.
          - `categoryId` string — YouTube video category ID. Defaults to 22 (People & Blogs). Common: 1 (Film), 2 (Autos), 10 (Music), 15 (Pets), 17 (Sports), 20 (Gaming), 23 (Comedy), 24 (Entertainment), 25 (News), 26 (Howto), 27 (Education), 28 (Science & Tech).
          - `playlistId` string — Optional YouTube playlist ID to add the video to after upload (e.g. 'PLxxxxxxxxxxxxx'). Use GET /v1/accounts/{id}/youtube-playlists to list available playlists. Works for both immediate and scheduled uploads. Quota cost: 50 YouTube API units per call.
        - object — Text and single image only (no videos). Supports STANDARD, EVENT, OFFER, and ALERT post types. Posts appear on GBP, Google Search, and Maps. Use locationId for multi-location posting. Schedule dates accept both ISO 8601 strings (e.g. '2026-04-15T09:00:00Z') and Google's native {year, month, day} objects.
          - `locationId` string — Target GBP location ID (e.g. "locations/123456789"). If omitted, uses the default location. Use GET /v1/accounts/{id}/gmb-locations to list locations.
          - `languageCode` string — BCP 47 language code (e.g. "en", "de", "es"). Auto-detected if omitted. Set explicitly for short or mixed-language posts.
          - `topicType` 'STANDARD' | 'EVENT' | 'OFFER' — Post type. STANDARD is a regular update. EVENT requires the event object. OFFER requires the offer object. Defaults to STANDARD if omitted.
          - `callToAction` object — Optional call-to-action button displayed on the post
            - `type` 'LEARN_MORE' | 'BOOK' | 'ORDER' | 'SHOP' | 'SIGN_UP' | 'CALL', required — Button action type: LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL
            - `url` string, uri, required — Destination URL for the CTA button (required when callToAction is provided)
          - `event` object — Event details. Required when topicType is EVENT. Google returns 400 if omitted for EVENT posts.
            - `title` string, required — Event name (displayed as the event heading on Google Search and Maps)
            - `schedule` object, required — Event date/time range. Uses Google's date format (NOT ISO 8601).
              - …
          - `offer` object — Offer details. Required when topicType is OFFER. All fields are optional per Google's API, but at least one is recommended.
            - `redeemOnlineUrl` string, uri — URL where the offer can be redeemed online
            - `termsConditions` string — Terms and conditions for the offer
            - `couponCode` string — Coupon code for the offer
        - object — Photo carousels up to 35 images. Video titles up to 2200 chars, photo titles truncated to 90 chars. privacyLevel must match creator_info options. Both camelCase and snake_case accepted. Creator Inbox (draft mode): Set draft: true to send content to the TikTok Creator Inbox instead of publishing immediately. The creator receives an inbox notification and completes the post using TikTok's editing flow. This maps to TikTok's post_mode: "MEDIA_UPLOAD" internally. Important: The field publish_type is NOT supported. Use draft: true for Creator Inbox flow. Photo drafts use the /v2/post/publish/content/init/ endpoint with post_mode: "MEDIA_UPLOAD". Video drafts use the dedicated /v2/post/publish/inbox/video/init/ endpoint. When draft: true, the video.upload scope is required. When draft is false or omitted (direct post), the video.publish scope is required. For Creator Inbox, TikTok app version must be 31.8 or higher.
          - `draft` boolean — When true, sends the post to the TikTok Creator Inbox as a draft instead of publishing immediately. The creator receives an inbox notification to complete posting via TikTok's editing flow. Maps to TikTok API post_mode: "MEDIA_UPLOAD" (photos) or the dedicated inbox endpoint (videos). When false or omitted, publishes directly via post_mode: "DIRECT_POST". Note: publish_type is not a supported field. Use this field instead.
          - `privacyLevel` string — One of the values returned by the TikTok creator info API for the account
          - `allowComment` boolean — Allow comments on the post
          - `allowDuet` boolean — Allow duets (required for video posts)
          - `allowStitch` boolean — Allow stitches (required for video posts)
          - `commercialContentType` 'none' | 'brand_organic' | 'brand_content' — Type of commercial content disclosure. Sufficient on its own: "brand_organic" ("Your Brand") implies isBrandOrganicPost and "brand_content" ("Branded Content", paid partnership) implies brandPartnerPromote, so you don't need to send the boolean flags separately. Branded content cannot be posted with privacyLevel SELF_ONLY.
          - `brandPartnerPromote` boolean — Whether the post promotes a brand partner (branded content / paid partnership). Only needed to disclose BOTH types at once (set it alongside commercialContentType "brand_organic"), or to override the value implied by commercialContentType.
          - `isBrandOrganicPost` boolean — Whether the post promotes the creator's own brand (brand organic). Only needed to disclose BOTH types at once (set it alongside commercialContentType "brand_content"), or to override the value implied by commercialContentType.
          - `contentPreviewConfirmed` boolean — User has confirmed they previewed the content
          - `expressConsentGiven` boolean — User has given express consent for posting
          - `mediaType` 'video' | 'photo' — Optional override. Defaults based on provided media items.
          - `videoCoverTimestampMs` integer — Optional for video posts. Timestamp in milliseconds to select which frame to use as thumbnail (defaults to 1000ms/1 second). Ignored when videoCoverImageUrl is provided.
          - `videoCoverImageUrl` string, uri — Optional for video posts. URL of a custom thumbnail image (JPG, PNG, or WebP, max 20MB). The image is stitched as a single frame at the start of the video and used as the cover. Overrides videoCoverTimestampMs when provided.
          - `photoCoverIndex` integer — Optional for photo carousels. Index of image to use as cover, 0-based (defaults to 0/first image).
          - `autoAddMusic` boolean — When true, TikTok may add recommended music (photos only)
          - `videoMadeWithAi` boolean — Set true to disclose AI-generated content
          - `description` string — Optional long-form description for photo posts (max 4000 chars). Recommended when content exceeds 90 chars, as photo titles are auto-truncated.
        - object — Text, images (up to 10), videos (up to 10), and mixed media albums. Captions up to 1024 chars for media, 4096 for text-only.
          - `parseMode` 'HTML' | 'Markdown' | 'MarkdownV2' — Text formatting mode for the message (default is HTML)
          - `disableWebPagePreview` boolean — Disable link preview generation for URLs in the message
          - `disableNotification` boolean — Send the message silently (users will receive notification without sound)
          - `protectContent` boolean — Protect message content from forwarding and saving
        - object — Requires a Public Profile. Single media item only. Content types: story (ephemeral 24h), saved_story (permanent, title max 45 chars), spotlight (video, max 160 chars).
          - `contentType` 'story' | 'saved_story' | 'spotlight' — Content type: story (ephemeral 24h, default), saved_story (permanent on Public Profile), spotlight (video feed)
        - object — Posts are either link (with URL/media), native video (via nativeVideo), or self (text-only). Use forceSelf to override. Subreddit defaults to the account's configured one. Some subreddits require a flair.
          - `subreddit` string — Target subreddit name (without "r/" prefix). Overrides the default. Use GET /v1/accounts/{id}/reddit-subreddits to list options.
          - `title` string — Post title. Defaults to the first line of content, truncated to 300 characters.
          - `url` string, uri — URL for link posts. If provided (and forceSelf is not true), creates a link post instead of a text post.
          - `forceSelf` boolean — When true, creates a text/self post even when a URL or media is provided.
          - `flairId` string — Flair ID for the post. Required by some subreddits. Use GET /v1/accounts/{id}/reddit-flairs?subreddit=name to list flairs.
          - `flairText` string — Custom flair text, for subreddits that allow free-text flair. Ignored when flairId is provided (flairId wins).
          - `nsfw` boolean — Mark the post as NSFW (Not Safe For Work / over 18).
          - `spoiler` boolean — Mark the post as a spoiler. The subreddit must have spoiler tagging enabled for this to take effect.
          - `sendreplies` boolean — Whether to receive inbox replies for comments on this post. Set to false to opt out.
          - `nativeVideo` boolean — Controls Reddit's native video upload flow. When true (default for video mediaItems), the video is uploaded to Reddit's CDN and submitted with kind=video so it renders as an embedded Reddit video player. Reddit transcodes server-side (1080p/30fps cap). Set to false to fall back to a legacy link post. If the subreddit blocks video posts, the upload falls back to a link post automatically.
          - `videogif` boolean — When true (and nativeVideo is active), submits the video as a silent videogif (kind=videogif). Use for short looping clips without audio.
          - `videoPosterUrl` string, uri — Optional poster/thumbnail image URL for native video posts. If omitted, the first frame of the video is extracted and used automatically.
        - object — Bluesky post settings. Supports text posts with up to 4 images or a single video. threadItems creates a reply chain (Bluesky thread). Images exceeding 1MB are automatically compressed. Alt text supported via mediaItem properties.
          - `threadItems` object[] — Complete sequence of posts in a Bluesky thread. The first item becomes the root post, subsequent items are chained as replies. When threadItems is provided, the top-level content field is used only for display and search purposes, it is NOT published. You must include your first post as threadItems[0].
            - `content` string
            - `mediaItems` MediaItem[]
              - …
        - object — Discord message settings. Supports plain text (2,000 chars), rich embeds (up to 10), native polls, forum posts, threads, and announcement crossposts. Media attachments support images (JPEG, PNG, GIF, WebP), videos (MP4), and documents (up to 10 files, 25 MB each). Webhook identity (username + avatar) can be customized per-account via PATCH /v1/connect/discord or per-post via webhookUsername/webhookAvatarUrl.
          - `channelId` string, required — Target channel snowflake ID. Determines which channel in the connected server receives the message.
          - `embeds` object[] — Up to 10 Discord embed objects (combined max 6,000 characters across all embeds). Sent alongside or instead of plain-text content.
            - `title` string — Embed title (max 256 chars)
            - `description` string — Embed body text (max 4,096 chars)
            - `url` string — URL the title links to
            - `color` integer — Embed accent color as decimal integer (e.g. 5814783 for blue). Convert hex to decimal.
            - `image` object
              - …
            - `thumbnail` object
              - …
            - `footer` object
              - …
            - `author` object
              - …
            - `fields` object[] — Up to 25 fields per embed
              - …
          - `poll` object — Native Discord poll. Cannot be combined with media attachments in the same message.
            - `question` object
              - …
            - `answers` object[] — 1-10 answer options
              - …
            - `duration` integer — Poll duration in hours (1-768). Default 24.
            - `allow_multiselect` boolean — Allow users to select multiple answers. Default false.
          - `crosspost` boolean — Auto-crosspost to every server following this announcement channel (type 5). No-op for regular text channels.
          - `forumThreadName` string — Thread title for forum channel posts (type 15). Required when posting to a forum channel.
          - `forumAppliedTags` string[] — Tag snowflake IDs to apply to forum posts. Max 5 tags.
          - `threadFromMessage` object — Create a follow-up thread under the published message.
            - `name` string — Thread name (1-100 chars)
            - `autoArchiveDuration` 60 | 1440 | 4320 | 10080 — Auto-archive after inactivity (minutes)
            - `rateLimitPerUser` integer — Slow-mode duration in seconds (0-21600)
          - `tts` boolean — Send as text-to-speech message. Discord reads the message aloud in the channel.
          - `webhookUsername` string — Override the webhook display name for this post only (1-80 chars). Falls back to the account-level default set via PATCH /v1/connect/discord.
          - `webhookAvatarUrl` string — Override the webhook avatar URL for this post only. Falls back to the account-level default.
        - object — Slack message settings. Posts mrkdwn text (up to 40,000 chars; Slack truncates beyond that) to the channel fixed by the connected account, with up to 10 media files per post uploaded via Slack's file API (the text becomes the caption). The target channel is chosen at connect time — one connected account per channel — so channelId is NOT accepted here (a 400 is returned); connect the desired channel via /v1/connect/slack and target its accountId. Messages over 4,000 characters cannot be edited later (Slack's edit limit is stricter than its post limit).
          - `threadTs` string — Parent message ts to post this message as a thread reply (e.g. "1503435956.000247").
          - `unfurlLinks` boolean — Expand links in the message into preview cards. Default true.
          - `unfurlMedia` boolean — Expand media links into inline previews. Default true.
          - `username` string — Override the bot display name for this message only (requires no setup; shown with an APP badge). Does not change the app identity in the sidebar.
          - `iconUrl` string — Override the bot avatar image URL for this message only.
      - `status` string — Platform-specific status: pending, publishing, published, failed
      - `platformPostId` string — The native post ID on the platform (populated after successful publish)
      - `platformPostUrl` string, uri — Public URL of the published post. Included in the response for immediate posts; for scheduled posts, fetch via GET /v1/posts/{postId} after publish time.
      - `publishedAt` string, date-time — Timestamp when the post was published to this platform
      - `removedFromPlatformAt` string, date-time, nullable — Set when a post that was successfully published later disappears from the platform (deleted on-platform or taken down by the platform). status stays "published" (it reflects the publish outcome); poll this field to detect post-publish removals. Absent while the post is live, and cleared if the post reappears. Detection runs with the analytics sync, so expect up to a few hours of lag.
      - `isTrialReel` boolean — Present and true only when this Instagram reel was launched as a Trial through Zernio (created with platformSpecificData.trialParams). Use it to segment trial reels in analytics. Note: Instagram's Graph API exposes no readable trial field, so this reflects creation-time intent only. It indicates the reel STARTED as a trial, not whether or when it graduated.
      - `trialGraduationStrategy` 'MANUAL' | 'SS_PERFORMANCE' — Graduation strategy the trial reel was launched with. Present only when isTrialReel is true.
      - `errorMessage` string — Human-readable error message when status is failed. Contains platform-specific error details explaining why the publish failed.
      - `errorCategory` 'auth_expired' | 'user_content' | 'user_abuse' | 'account_issue' | 'platform_rejected' | 'platform_error' | 'system_error' | 'unknown' — Error category for programmatic handling: auth_expired (token expired/revoked), user_content (wrong format/too long), user_abuse (rate limits/spam), account_issue (config problems), platform_rejected (policy violation), platform_error (5xx/maintenance), system_error (Zernio infra), unknown
      - `errorSource` 'user' | 'platform' | 'system' — Who caused the error: user (fix content/reconnect), platform (outage/API change), system (Zernio issue, rare)
    - `scheduledFor` string, date-time
    - `timezone` string
    - `status` 'draft' | 'scheduled' | 'publishing' | 'published' | 'failed' | 'partial'
    - `tags` string[] — YouTube constraints: each tag max 100 chars, combined max 500 chars, duplicates removed.
    - `hashtags` string[]
    - `mentions` string[] — Stored for reference only. This field does NOT automatically create @mentions when publishing. For LinkedIn @mentions, use the /v1/accounts/{accountId}/linkedin-mentions endpoint to resolve profile URLs to URNs, then embed the returned mentionFormat directly in the post content field.
    - `visibility` 'public' | 'private' | 'unlisted'
    - `metadata` object
    - `recycling` RecyclingState — Current recycling configuration and state on a post
      - `enabled` boolean — Whether recycling is currently active
      - `gap` integer — Number of interval units between reposts
      - `gapFreq` 'week' | 'month' — Interval unit (week or month)
      - `startDate` string, date-time
      - `expireCount` integer
      - `expireDate` string, date-time
      - `contentVariations` string[] — Content variations for recycled copies (if configured)
      - `contentVariationIndex` integer — Current position in the content variations rotation (read-only)
      - `recycleCount` integer — How many recycled copies have been created so far (read-only)
      - `nextRecycleAt` string, date-time — When the next recycled copy will be created (read-only)
      - `lastRecycledAt` string, date-time — When the last recycled copy was created (read-only)
    - `recycledFromPostId` string — ID of the original post if this post was created via recycling
    - `queuedFromProfile` string — Profile ID if the post was scheduled via the queue
    - `queueId` string — Queue ID if the post was scheduled via a specific queue
    - `createdAt` string, date-time
    - `updatedAt` string, date-time

## Other responses

- `400` — Validation error
- `401` — Unauthorized
- `403` — Forbidden. Distinguish by the `code` field: - `ACCOUNT_DISCONNECTED` — a target account exists but its platform connection is no longer active (token expired or revoked, or the account was disconnected). Reconnect the account, then refresh account IDs from `GET /v1/accounts` (accounts report their connection state via `isActive`). The disconnect itself is also emitted as the `account.disconnected` webhook event. - `PROFILE_OVER_LIMIT` — a target account belongs to a profile beyond the plan's profile limit. - No `code` — a target `accountId` does not belong to the authenticated user (or is outside the API key's profile scope).
- `409` — Duplicate content detected. Returned when the requested post matches an existing one on `(platform, accountId, content-hash)` within the last 24 hours, AND the request was NOT an `x-request-id` retry of an in-flight call. Distinct from same-`x-request-id` retries (which return HTTP 200 with the original post — see operation description for the idempotency contract). Body fields: - `error` — human-readable message - `details.accountId` — the account that already has this content - `details.platform` — the platform that already has this content - `details.existingPostId` — Zernio `_id` of the original post To intentionally re-post identical content within 24h, vary the content fingerprint (change the caption, swap a media item, or use a different account). To avoid 409s caused by retry loops, set a unique `x-request-id` per logical request — see `parameters.x-request-id` above.
- `429` — Rate limit exceeded. Possible causes: API rate limit, velocity limit (25 posts/hour per account), account cooldown, or daily platform limits.

---

[API](https://skmtc.net/zernio/apis/zernio-api.md) · [All operations](https://skmtc.net/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/zernio/zernio-api/versions/51932b099b2f/schema)
