> For the complete documentation index, see [llms.txt](https://help.blotato.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.blotato.com/start-with-an-ai-agent/llm/publishing.md).

# Publishing requests

Build a REST publishing request with platform-specific fields, choose one scheduling method, and verify the saved submission.

Compact reference for agents making REST requests. All endpoint paths below are relative to `https://backend.blotato.com/v2`. For task instructions, read the [workflow guide](/agent-workflows/agent-workflows/publish.md). For tool calls, use the [MCP reference](/start-with-an-ai-agent/mcp/tools.md).

## Publishing a post

Resolve the requested account and destination before writing. A draft request does not authorize publishing. Check [Media Requirements](/rest-api-reference/publish-post/media.md), [Plan Limits](/settings/billing-and-credits.md#plan-limits), and the [scheduling guide](/api-and-mcp-concepts/scheduling.md) before submission.

```
SUPPORTED PUBLISHING PLATFORMS

Instagram, TikTok, LinkedIn, Facebook, X (Twitter), Threads, Bluesky, Pinterest, and YouTube.

This list is exhaustive for the Blotato web app, API, MCP, and Cowork. Blotato does not publish to platforms outside this list.

POST /posts

Minimal payload (Twitter):
{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Hello world",
      "mediaUrls": [],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  }
}

RULES:
- For social platforms, content.platform and target.targetType use the same value.
  The REST webhook exception uses content.platform="other", target.targetType="webhook".
- mediaUrls is required. Pass [] for text-only posts. Pass public URLs for media.
- accountId comes from GET /users/me/accounts
- No upload step is required for an existing, directly accessible media URL.
  A webpage URL, sign-in-protected link, or expired URL is not a usable media file.
- Before retrying a media failure, verify the target platform supports the media
  type, attachment count, file extension, dimensions, duration, and file size.
- For local files without a public URL, use POST /media/uploads to get a presigned upload URL:
  1. POST /media/uploads with {"filename": "photo.jpg"} -> returns {presignedUrl, publicUrl}
  2. PUT the file binary to presignedUrl with the correct Content-Type header.
  3. Wait for the upload to succeed, then use publicUrl in mediaUrls.
  A presigned URL alone does not upload the bytes. If the client's network policy
  blocks the upload host, ask for access to that host. Do not broaden network access
  automatically. Plan and platform file limits still apply.

SCHEDULING (optional, top-level fields alongside "post"):
- Choose one scheduling method, not both. Replace example timestamps with the
  user's requested future time after resolving their timezone and scheduling horizon.
- scheduledTime: ISO 8601 timestamp with timezone offset for a specific time.
- useNextFreeSlot: true selects an available slot matching the platform, account,
  and Page. It still triggers slot validation when scheduledTime is also present.
  An explicit time does not bypass a missing-slot error. Do not combine these fields.
- If NEITHER scheduledTime NOR useNextFreeSlot is provided, the post PUBLISHES IMMEDIATELY.
- Both fields MUST be root-level (siblings of "post"). If nested inside "post", "options", or any other object, they are IGNORED and the post publishes immediately.

Publish immediately (no scheduling fields):
{
  "post": { "accountId": "98432", "content": {...}, "target": {...} }
}

Schedule at user's next free calendar slot:
{
  "post": { "accountId": "98432", "content": {...}, "target": {...} },
  "useNextFreeSlot": true
}

Schedule at a specific time:
{
  "post": { "accountId": "98432", "content": {...}, "target": {...} },
  "scheduledTime": "2025-12-25T15:00:00Z"
}

WRONG (do NOT nest scheduling fields inside "post" or "options"):
{
  "post": { "accountId": "98432", "content": {...}, "target": {...}, "useNextFreeSlot": true }
}
{
  "post": { "accountId": "98432", "content": {...}, "target": {...} },
  "options": { "scheduledTime": "2026-03-04T16:30:00+00:00" }
}

THREADS (Twitter, Bluesky, Threads):
Use content.additionalPosts[] to create a thread in a single API call.
The first tweet goes in content.text. Additional tweets go in additionalPosts[].

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "First tweet in the thread (1/3)",
      "mediaUrls": [],
      "platform": "twitter",
      "additionalPosts": [
        { "text": "Second tweet (2/3)", "mediaUrls": [] },
        { "text": "Third tweet (3/3)", "mediaUrls": [] }
      ]
    },
    "target": { "targetType": "twitter" }
  }
}

Each additionalPosts[] entry has: text (string), mediaUrls (array of strings).
Blotato handles reply chaining. You do NOT need to capture tweet IDs.
Works for: twitter, bluesky, threads.

PLATFORM-SPECIFIC TARGET FIELDS (set these inside "target" alongside "targetType"):

twitter:
  (no extra fields required)

linkedin:
  pageId (optional) - LinkedIn Company Page ID from subaccounts endpoint. Omit for personal profile.
  carousels (LinkedIn Document / PDF carousel): pass 2-10 image URLs (JPG, PNG) in content.mediaUrls and Blotato auto-builds a LinkedIn Document carousel — LinkedIn's modern PDF-based carousel format, viewers swipe through pages like a PDF. This is the same format you'd use for Instagram carousels — pass the same image URLs to LinkedIn and Blotato handles the conversion. Videos are not supported in carousels. Max 10 images.

facebook:
  pageId (REQUIRED) - from GET /users/me/accounts/{accountId}/subaccounts
  mediaType - "reel" REQUIRED for videos (regular feed videos no longer supported), "story" for Stories, omit for text/image posts. Stories require one video or image attachment (only the first is used if multiple provided).
  link (optional) - URL to attach as link preview
  firstComment (optional) - auto-posts as the first comment right after publishing. Up to 8000 chars. Posts and reels only, NOT stories.
  Reel video specs: MP4/MOV, max 512 MB, min 540x960, 9:16 recommended, 3 seconds to 4 hours, 24-60 fps
  Story video specs: MP4/MOV, max 512 MB, 9:16 recommended, 3-60 seconds
  Other video formats require conversion to MP4. Automatic conversion, cropping, and downscaling run only on videos of 120 seconds or less. Longer videos must already meet the publishing requirements.

instagram:
  mediaType (optional) - "reel" or "story". Default: "reel" for videos. Set "story" to publish one image or video as a Story.
  Stories do not support API captions. Set the required content.text field to "". Render visible text into the image or video before publishing. Native Story text and interactive stickers require manual editing in Instagram.
  Automatic video conversion requires duration <=120 seconds. Instagram conversion downscales width*height >2,073,600 pixels and reduces bitrate >25 Mbps to 20 Mbps. It does not force a 9:16 crop. For longer portrait Reels, export at 1080x1920 or 720x1280 with H.264/AAC, bitrate <=25 Mbps, size <=300 MB, and duration <=15 minutes. The 9:16 frame is export guidance, not an automatic crop rule.
  altText (optional) - alt text for images, up to 1000 characters
  collaborators (optional) - array of Instagram handles (without @), max 3. Single image posts and reels only - NOT supported on carousels (multi-media posts), which will fail to publish.
  coverImageUrl (optional) - cover image URL for reels, max 8MB
  shareToFeed (optional) - boolean, share the reel to feed
  audioName (optional) - custom audio name for reels (can only set once)
  firstComment (optional) - auto-posts as the first comment right after publishing. Up to 2200 chars. Posts, carousels, and reels, NOT stories. Useful for links.
  trial (optional) - object for trial reels (shown to non-followers first). Only for reels.
    graduationStrategy (REQUIRED inside trial) - "MANUAL" or "SS_PERFORMANCE"
    MANUAL = you promote to followers manually. SS_PERFORMANCE = Instagram auto-promotes based on performance.

tiktok (ALL of these are REQUIRED):
  privacyLevel - "SELF_ONLY", "PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "FOLLOWER_OF_CREATOR"
  disabledComments - boolean
  disabledDuet - boolean
  disabledStitch - boolean
  isBrandedContent - boolean
  isYourBrand - boolean
  isAiGenerated - boolean
  (optional) title - for image posts, max 90 chars
  (optional) autoAddMusic - boolean, for photo posts only
  (optional) isDraft - boolean, save as draft
  (optional) imageCoverIndex - number, cover image index for carousels (starts from 0)
  (optional) videoCoverTimestamp - number, milliseconds for video cover frame

pinterest:
  boardId (REQUIRED) - from GET /social/pinterest/boards?accountId={accountId}
  title (optional)
  altText (optional)
  link (optional)

threads:
  replyControl (optional) - "everyone", "accounts_you_follow", "mentioned_only"

bluesky:
  (no extra fields required)

youtube:
  title (REQUIRED) - video title
  privacyStatus (REQUIRED) - "private", "public", "unlisted"
  shouldNotifySubscribers (REQUIRED) - boolean
  isMadeForKids (optional) - boolean, default false
  containsSyntheticMedia (optional) - boolean, for AI-generated content
  playlistIds (optional) - array of playlist IDs to add the video to. Get from GET /users/me/accounts/{accountId}/subaccounts
  thumbnailUrl (optional) - publicly accessible image URL for custom thumbnail. Requires verified YouTube account with custom thumbnail capabilities.
  YouTube "description" comes from the content.text field. Tags are not supported.

webhook (REST only, not an MCP publish platform):
  content.platform = "other", target.targetType = "webhook"
  url (REQUIRED) - the webhook URL

Response: { "postSubmissionId": "uuid" }
Save postSubmissionId. Acceptance does not prove publication.

Poll: GET /posts/{postSubmissionId}
Status values: "in-progress" | "scheduled" | "published" | "failed"
- scheduled: report scheduledTime and stop immediate-publication polling
- published: report publication; include publicUrl only if returned (the field is optional)
- failed: stop and inspect errorMessage before any retry
- in-progress: unresolved; it is also the fallback when no matching result is found
Poll at least 10 seconds apart within a bounded wait. Preserve the saved ID and
pending state if the wait ends. Do not repeat POST /posts because polling timed out.
Track each destination separately. Do not repeat successful posts in a partial failure.
```

Return to the [agent reference index](/start-with-an-ai-agent/llm.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.blotato.com/start-with-an-ai-agent/llm/publishing.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
