> 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.md).

# API reference for agents

This single-document reference includes client setup, authentication, all MCP tool names and input fields, REST operations and examples, platform requirements, and completion rules. Read the sections below without following links to learn the API and MCP workflows. Links to tutorials and template-specific examples are supplementary. Retrieve each template's current inputs before generating a visual.

## Connection and verification

1. For MCP, configure Streamable HTTP at `https://mcp.blotato.com/mcp` with OAuth or the `blotato-api-key` header supported by your client.
2. For REST, use `https://backend.blotato.com/v2` with the `blotato-api-key` header. Keep keys out of chat messages, documentation, and shared configuration.
3. Verify MCP with `blotato_list_accounts`, or REST with `GET /v2/users/me/accounts`. An empty account list is a successful authenticated result.
4. Select social accounts from returned IDs. Connect accounts through [Blotato Settings](https://my.blotato.com/settings) before publishing.

Reading this document does not install a server or authorize an account. An installed plugin with no connected account or no tools has not passed verification. OAuth authorization and a successful tool call are separate checks.

## Execution rules

1. Use only tools exposed by the connected client. REST and MCP do not expose identical operations or argument shapes.
2. Read the selected tool's schema. Get account, Page, board, playlist, post, schedule, and template IDs from responses rather than inventing them.
3. Get the user's approval for the intended write operation. Do not publish, send messages, activate automations, delete content, or purchase credits as a connection test.
4. Pass public media URLs directly. For a local file, request upload URLs, PUT the bytes, verify upload success, then use the returned public URL.
5. Save returned IDs. Follow each operation's completion states below. A submitted or scheduled request is not proof of publication.
6. After an uncertain write response, check existing work before retrying. A repeated create request risks duplicate content.
7. Report the returned status, error, scheduled time, or published URL. Do not claim unsupported features or unverified client compatibility.

## Contents

* [Use Blotato with an AI agent](#api-agents)
* [Search and read the help docs](#api-docs-search)
* [Connect your AI client](#api-mcp-setup)
* [API access and authentication](#api-guides-authentication)
* [REST API versus MCP](#api-guides-rest-vs-mcp)
* [MCP input index](#api-mcp-inputs)
* [Tools Reference](#api-mcp-tools)
* [Accounts and identifiers](#api-guides-accounts-and-identifiers)
* [Async jobs and polling](#api-guides-async-jobs-and-polling)
* [Media uploads and conversion](#api-guides-media-uploads-and-conversion)
* [Post lifecycle](#api-guides-post-lifecycle)
* [Scheduling posts](#api-guides-scheduling)
* [API rate limits](#api-guides-rate-limits)
* [Errors and retries](#api-guides-error-handling)
* [Diagnose and answer a support question](#support-agent-triage)
* [Find a post's outcome before retrying](#support-publishing-status)
* [Publish and verify a post](#api-agent-workflows-publish)
* [Schedule and manage posts](#api-agent-workflows-schedule)
* [Upload local media](#api-agent-workflows-upload)
* [Create and publish a visual](#api-agent-workflows-visuals)
* [Extract and repurpose content](#api-agent-workflows-sources)
* [Read post analytics](#api-agent-workflows-analytics)
* [Read and reply to comments](#api-agent-workflows-comments)
* [Read and reply to messages](#api-agent-workflows-messages)
* [Manage DM automations](#api-agent-workflows-automations)
* [Check or add credits](#api-agent-workflows-credits)
* [REST API quickstart](#api-start)
* [Public API operation index](#api-api-reference-operations)
* [Users](#api-api-reference-users)
* [Accounts](#api-api-reference-accounts)
* [Credits /v2/credits](#api-api-reference-credits)
* [Upload Media /v2/media](#api-api-reference-upload-media-v2-media)
* [Publish Post /v2/posts](#api-api-reference-publish-post)
* [Get Post /v2/posts/:postSubmissionId](#api-api-reference-get-post)
* [List Posts /v2/posts](#api-api-reference-list-posts)
* [List Published Posts /v2/published-posts](#api-api-reference-list-published-posts)
* [Analytics /v2/analytics](#api-analytics)
* [Analytics Metrics Reference](#api-analytics-metrics)
* [Comments /v2/comments](#api-api-reference-comments)
* [Messages /v2/messages](#api-api-reference-messages)
* [DM Automations /v2/dm-automations](#api-api-reference-dm-automations)
* [Create Source /v2/source-resolutions-v3](#api-api-reference-create-source)
* [Get Source /v2/source-resolutions-v3/:id](#api-api-reference-get-source)
* [Visual Templates](#api-visuals-README)
* [Create Visual /v2/videos/from-templates](#api-api-reference-create-video)
* [Get Visual Status /v2/videos/creations/:id](#api-api-reference-find-video)
* [Delete Video /v2/videos/:id](#api-api-reference-delete-video)
* [Scheduled Posts /v2/schedules](#api-api-reference-schedules)
* [Schedule Slots](#api-api-reference-schedule-slots)
* [Instagram: API and MCP publishing](#api-platforms-instagram)
* [Facebook: API and MCP publishing](#api-platforms-facebook)
* [TikTok: API and MCP publishing](#api-platforms-tiktok)
* [YouTube: API and MCP publishing](#api-platforms-youtube)
* [LinkedIn: API and MCP publishing](#api-platforms-linkedin)
* [Pinterest: API and MCP publishing](#api-platforms-pinterest)
* [X (Twitter): API and MCP publishing](#api-platforms-twitter)
* [Threads: API and MCP publishing](#api-platforms-threads)
* [Bluesky: API and MCP publishing](#api-platforms-bluesky)
* [Async Workflows](#api-recipes-workflows)

## Use Blotato with an AI agent

This is the entry point for customer assistants executing tasks and support agents answering questions. Website instructions describe actions an agent performs or explains to the customer. They are not interchangeable with REST or MCP calls.

### Route the request

Use context already supplied. Ask only for a missing fact which changes the next action.

1. Identify the requested outcome and whether you are executing it or explaining it.
2. Identify the surface: Blotato website, MCP, REST, n8n, or Make.
3. Identify the client, destination platform, and failing stage when relevant.
4. Identify the artifact: workflow JSON, local media file, hosted media URL, generated visual, draft, scheduled item, or published post.
5. Check which provider owns the endpoint, key, or node. A Blotato key does not authenticate another provider.

For support, follow [Diagnose and answer](#support-agent-triage). Answer the customer's question before offering a tutorial. Preserve completed checks and human corrections.

### Website Modes and Current Navigation

Developer Platform is the website navigation for monitoring API, MCP, and automation work. Content Studio is the navigation for the in-app AI Agent and visual creation. Switching modes does not change the subscription, accounts, or content, and does not connect an external assistant.

* In-app AI Agent: Content Studio > New Post. New draft inside the workspace creates a blank draft, not AI output.
* Connect or reconnect social destinations: Accounts > Connect account, or the existing account's actions menu > Reconnect. Accounts is no longer a Settings tab.
* Publication results: Posts > Scheduled, Published, or Failed. Select the platform and time window before concluding a post is missing.
* Recurring slots: Calendar > Weekly schedule, within the Weekly posting schedule section.
* Performance rankings: Analytics > Top posts, not the old Published > Top Performing tab.
* API activity and errors: Developer Platform > Logs.
* Comments and messages: Inbox > Posts or Chats.
* Inspiration, Prompts, and Viral AI Coach: Tools.
* Credentials and client setup: Settings > API. Language and reset controls: Settings > Profile. Credits and subscription: Settings > Billing.

Use [Website modes and navigation](/navigation.md) for the screen-by-screen map and screenshots. The draft's Photo / Video control uploads existing media. Do not send users there to find the old image-generation menu.

### Connect and verify

1. Select the exact client in [MCP setup](#api-mcp-setup), or use the [REST quickstart](#api-start) for HTTP, n8n, or Make.
2. Confirm paid access. Generating an API key during a trial starts billing. Read [authentication](#api-guides-authentication) before generating a key.
3. Call `blotato_list_accounts`, or `GET /v2/users/me/accounts`.
4. Select the requested social destination from the response. Connect missing accounts in [Accounts](https://my.blotato.com/accounts).

An empty list proves authentication, not publishing readiness. A saved connector, OAuth redirect, or “Connected” label alone does not prove a tool call works. ChatGPT.com and Codex have separate setup paths.

### Choose a task

| Outcome                              | Execute through API or MCP                                   | Explain website actions                                                            |
| ------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| Connect a social destination         | [IDs and destinations](#api-guides-accounts-and-identifiers) | [Social account settings](/settings/social-accounts.md)                            |
| Publish or cross-post                | [Publish and verify](#api-agent-workflows-publish)           | [Create posts](/web-app-getting-started/posts.md)                                  |
| Schedule, reschedule, or cancel      | [Manage schedules](#api-agent-workflows-schedule)            | [Calendar](/web-app-getting-started/calendar.md)                                   |
| Upload or use existing media         | [Upload and prepare media](#api-agent-workflows-upload)      | [Attach media](/support/faqs.md#how-do-i-create-an-instagram-reel-or-tiktok-video) |
| Generate images, carousels, or video | [Generate and verify](#api-agent-workflows-visuals)          | [Video templates](/web-app-features/videos.md)                                     |
| Extract or repurpose source content  | [Extract a source](#api-agent-workflows-sources)             | [Context](/web-app-getting-started/sources.md)                                     |
| Inspect performance                  | [Analytics](#api-agent-workflows-analytics)                  | [Checkpoints and pending data](/web-app-features/analytics.md)                     |
| Read or reply to public comments     | [Comments](#api-agent-workflows-comments)                    | [Inbox](/web-app-features/inbox.md)                                                |
| Read or reply to private messages    | [Messages](#api-agent-workflows-messages)                    | [Inbox](/web-app-features/inbox.md)                                                |
| Set up keyword-triggered replies     | [DM automations](#api-agent-workflows-automations)           | [DM automation guide](/support/dm-automations.md)                                  |
| Check balance or purchase credits    | [Credits](#api-agent-workflows-credits)                      | [Billing and credits](/settings/billing-and-credits.md)                            |
| Diagnose a failure                   | [Errors and retries](#api-guides-error-handling)             | [Support diagnosis](#support-agent-triage)                                         |

### Instructions for the agent

1. Use only available tools and the selected operation's schema. [REST and MCP](#api-guides-rest-vs-mcp) differ in fields and operation coverage.
2. Retrieve current IDs. After reconnection, re-list accounts. Never substitute a social-platform post ID, submission ID, or schedule ID for another ID type.
3. Check [platform requirements](/platform-publishing/platforms.md) and [media requirements](/rest-api-reference/publish-post/media.md) before a write.
4. Confirm the requested destination and authorization. Connection tests must not publish, send messages, purchase credits, or activate automations.
5. Record returned IDs. Use [operation-specific completion rules](#api-guides-async-jobs-and-polling), including error fields inside successful HTTP or MCP responses.
6. Inspect existing work after an uncertain write. Do not blindly repeat a create request.
7. Report the verified status, scheduled time, or returned media/public URL. Explain what remains unresolved.

### Documentation for retrieval

Start with the task page, then retrieve only the contract or diagnosis needed.

| Resource                                                          | Use                                                                      |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------ |
| [Search and read help](#api-docs-search)                          | Optional public documentation MCP lookup, separate from account access   |
| [MCP tools](#api-mcp-tools)                                       | Available tools and behavior                                             |
| [MCP input index](#api-mcp-inputs)                                | Required and optional tool arguments                                     |
| [API reference for agents](/start-with-an-ai-agent/llm.md)        | Generated setup, contracts, examples, and completion rules in 1 document |
| [Documentation index](https://help.blotato.com/llms.txt)          | Find a page outside this task index                                      |
| [Full documentation text](https://help.blotato.com/llms-full.txt) | Whole-corpus retrieval when required                                     |
| [OpenAPI JSON](https://backend.blotato.com/openapi.json)          | Published REST schemas                                                   |

Reading documentation does not install a server, authorize an account, or execute a task.

***

## Search and read the help docs

Use the [task index](#api-agents--choose-a-task) when the outcome is known. For another question, search published help pages through the documentation MCP server. This is an optional lookup route, not another requirement for publishing.

### Choose the server

| Purpose                             | Server                                                              | Access                                                                      |
| ----------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Look up published help              | [Documentation MCP endpoint](https://help.blotato.com/~gitbook/mcp) | Public documentation. No Blotato API key or account authorization required. |
| Work with connected social accounts | `https://mcp.blotato.com/mcp`                                       | Follow [client setup](#api-mcp-setup) and verify account access.            |

The documentation server does not connect social accounts or publish posts. Its results describe published documentation, not unpublished repository edits.

### Find the answer

1. Add the documentation endpoint to an HTTP MCP client under a distinct name such as `blotato-docs`, if a docs connection is needed.
2. Discover its tools with `tools/list`.
3. Call `searchDocumentation` with a `query` string.
4. Select a result for the customer's surface: API/MCP execution or Blotato website instructions.
5. Call `getPage` with the result's page URL in `url`.
6. Read the page before answering or constructing a request.

| Tool                  | Input   | Use                                            |
| --------------------- | ------- | ---------------------------------------------- |
| `searchDocumentation` | `query` | Retrieve matching help-page excerpts and URLs. |
| `getPage`             | `url`   | Retrieve the selected page's Markdown.         |

Example search input:

```json
{"query":"schedule timezone"}
```

A search result about a website button is not an API parameter reference. A result about API scheduling is not a website walkthrough. Search rank alone does not establish relevance or correctness. Follow the [task index](#api-agents--choose-a-task) when results mix surfaces.

Keep API keys and customer data out of documentation queries. Use search and page retrieval for lookup. The server also advertises `askQuestion` and `sendFeedback`. Feedback sends a report, so require the user's request before using it.

### Without a docs MCP connection

1. Read the [task index](#api-agents--choose-a-task) or [documentation index](https://help.blotato.com/llms.txt).
2. Fetch the selected page as Markdown by appending `.md` to its published URL.

For account actions, return to [Blotato client setup](#api-mcp-setup). Reading help documentation does not verify access to a customer's connected accounts.

The hosted documentation endpoint is provided by GitBook. See [GitBook's published-docs MCP requirements](https://gitbook.com/docs/ai-for-your-readers/mcp-servers-for-published-docs).

***

## Connect your AI client

Choose your client below. Blotato's MCP server runs remotely at `https://mcp.blotato.com/mcp` using Streamable HTTP.

### Before you start

1. Confirm your paid Blotato subscription. API and MCP access is not included in the free trial.
2. Connect social accounts in [Accounts](https://my.blotato.com/accounts).
3. For an API-key setup, copy the full key from [Settings > API](https://my.blotato.com/settings/api). Generating a key during a trial ends the trial and starts billing.

An account connection is needed for publishing. You still receive a successful empty account list before connecting social accounts.

### Choose your client

Open [Settings > API](https://my.blotato.com/settings/api) for the client chooser. Select the app you use, not another app with a similar chat interface. ChatGPT.com and ChatGPT Work / Codex have separate setup instructions. A Blotato subscription is separate from your AI client's subscription.

After setup, ask your client to list connected Blotato accounts. A Connected badge alone does not verify a tool call. A successful empty list means the connection works but no social accounts are connected.

#### Claude.ai

1. Open [Claude connectors](https://claude.ai/customize/connectors?modal=add-custom-connector).
2. Add a custom connector named `Blotato`.
3. Enter `https://mcp.blotato.com/mcp` as the URL.
4. Connect and approve access in the browser.
5. Ask: “List my connected Blotato accounts using `blotato_list_accounts`.”

Log into the intended Blotato account in the browser during authorization. If access fails, follow [OAuth troubleshooting](/start-with-an-ai-agent/mcp/faqs.md).

#### Claude Desktop / Cowork

1. Open Customize, then Connectors.
2. Add a custom connector named `Blotato`.
3. Enter `https://mcp.blotato.com/mcp` as the URL.
4. Connect and approve access in your browser.
5. Ask: “List my connected Blotato accounts using `blotato_list_accounts`.”

On a managed workspace, ask the workspace admin if adding connectors is restricted. If authorization repeatedly fails after reconnecting, follow the [stale-session steps](#api-mcp-setup--troubleshooting).

For local media, the assistant also needs file access and permission to reach the upload destination. Follow [upload troubleshooting](/start-with-an-ai-agent/mcp/faqs.md#claude-cant-upload-my-photos-or-files-to-blotato--sandbox-error--network-egress-blocked-in-claude-cowork-claude-desktop-or-claude-code).

Video: [Claude Cowork and Desktop setup](https://youtu.be/mh4Z9U-oaps).

#### Claude Code

1. Run this command in your terminal:

```bash
claude mcp add --transport http blotato https://mcp.blotato.com/mcp
```

2. Open Claude Code.
3. Run `/mcp`.
4. Select Blotato and authenticate through the browser.
5. Ask: “List my connected Blotato accounts using `blotato_list_accounts`.”

For an API-key setup instead of OAuth, use this command when adding the server. Replace the placeholder with your full API key:

```bash
claude mcp add --transport http blotato https://mcp.blotato.com/mcp --header "blotato-api-key: YOUR_API_KEY"
```

If Blotato is already configured, manage that entry through `/mcp` before adding it again. Keep API keys out of shared project configuration.

See [Claude Code's MCP documentation](https://code.claude.com/docs/en/mcp) and [Blotato's Claude Code tutorials](/integrations-and-automation-templates/claude-code.md).

#### Codex

1. Open Settings, then MCP servers, and add a server.
2. Name it `blotato`.
3. Select Streamable HTTP.
4. Enter `https://mcp.blotato.com/mcp` as the URL.
5. Under Headers, set `blotato-api-key` to your full API key.
6. Leave Bearer token env var blank when using the header above.
7. Save and restart the MCP connection or client.
8. Ask: “List my connected Blotato accounts using `blotato_list_accounts`.”

For Codex CLI with OAuth:

```bash
codex mcp add blotato --url https://mcp.blotato.com/mcp
codex mcp login blotato
```

For an API key in Codex's `~/.codex/config.toml`, use TOML:

```toml
[mcp_servers.blotato]
url = "https://mcp.blotato.com/mcp"
http_headers = { "blotato-api-key" = "YOUR_API_KEY" }
```

Replace the placeholder and keep the key in your personal configuration. The CLI also supports a bearer-token environment variable if your environment supplies it.

See [OpenAI's MCP configuration guide](https://learn.chatgpt.com/docs/extend/mcp).

#### ChatGPT.com / Work

Use [OpenAI's custom MCP connection flow](https://developers.openai.com/plugins/deploy/connect-chatgpt) on ChatGPT.com:

1. Enable Developer mode in Settings → Security and login.
2. Open [ChatGPT Plugins](https://chatgpt.com/plugins).
3. Select the + button.
4. Enter the name `Blotato`, a description, and server URL `https://mcp.blotato.com/mcp`.
5. Choose OAuth authentication.
6. Create the connection.
7. Complete the Blotato sign-in and review the requested access before approving.
8. Start a new chat with Blotato selected in the tools menu.
9. Ask: “List my connected Blotato accounts using `blotato_list_accounts`.”

If the plugin exists but has no connected account, open Settings → Plugins → Blotato → Connect another account → Sign in with Blotato. Adding the plugin and authorizing an account are separate steps.

“No app tools available yet” means the setup has not passed verification. Check account authorization and tool discovery before retrying the prompt. If sign-in fails, record the error and follow [authentication troubleshooting](#api-guides-authentication). Do not describe the connection as working until a tool call succeeds.

Availability depends on your account and workspace policy. This creates a custom connection, rather than installing a Blotato listing from the public directory. For local Codex configuration, use the Codex tab. Codex does not use ChatGPT.com's Developer mode toggle.

#### Cursor

1. Open your personal `~/.cursor/mcp.json` configuration.
2. Add the Blotato entry below, preserving any existing servers.
3. Replace `YOUR_API_KEY` with your full key.
4. Save and restart Cursor.
5. Ask: “List my connected Blotato accounts using `blotato_list_accounts`.”

```json
{
  "mcpServers": {
    "blotato": {
      "url": "https://mcp.blotato.com/mcp",
      "headers": {
        "blotato-api-key": "YOUR_API_KEY"
      }
    }
  }
}
```

Use Cursor's personal configuration for a literal API key. For variable substitution and workspace configuration, see [Cursor's MCP documentation](https://cursor.com/docs/mcp).

#### VS Code

For VS Code's MCP configuration:

1. Open `.vscode/mcp.json` in your workspace, preserving any existing servers.
2. Add the configuration below.
3. Start the Blotato server from the editor's MCP controls.
4. Enter your full Blotato API key when prompted.
5. Ask the chat assistant: “List my connected Blotato accounts using `blotato_list_accounts`.”

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "blotato-api-key",
      "description": "Blotato API key",
      "password": true
    }
  ],
  "servers": {
    "blotato": {
      "type": "http",
      "url": "https://mcp.blotato.com/mcp",
      "headers": {
        "blotato-api-key": "${input:blotato-api-key}"
      }
    }
  }
}
```

This uses VS Code's `servers` format, rather than Cursor's `mcpServers` format. Interactive input variables apply to the extension host. For VS Code Agent Host configuration and restrictions, follow the [VS Code MCP reference](https://code.visualstudio.com/docs/agents/reference/mcp-configuration).

#### Other clients

For Antigravity, Replit Agent, or another remote-MCP client:

1. Open the client's MCP configuration.
2. Select Streamable HTTP.
3. Set the URL to `https://mcp.blotato.com/mcp`.
4. Complete OAuth if supported, or set a `blotato-api-key` header to your full API key.
5. Reload the connection.
6. Ask the assistant to call `blotato_list_accounts`.

Some clients accept the JSON copied from [Settings > API](https://my.blotato.com/settings/api) under Other MCP Clients. Check the client's configuration format before pasting it. Codex uses TOML, and VS Code uses a different JSON wrapper.

For a form with a Headers section, enter the full key as the value for `blotato-api-key`. Leave Bearer token env var blank when using this header.

### Verify your connection

The verification call is `blotato_list_accounts`. It returns an array containing account IDs, platforms, names, and subaccounts. An empty array is a valid result when you have no connected accounts.

If no tool call runs, check whether the client enabled the connection. If the call returns an error, use the [authentication guide](#api-guides-authentication) and the troubleshooting steps below.

After verification, choose an [agent workflow](/agent-workflows/agent-workflows.md).

### Tutorials

* [Claude + Blotato beginner setup](https://www.youtube.com/watch?v=1dSNfnFL40c)
* [Build an AI assistant for social media](https://youtu.be/3HVH2Iuplqo)
* [Claude Code tutorial](https://youtu.be/fYX6hHC9FhQ)

### Upload your own local images or videos via MCP

The Blotato MCP tools (`blotato_create_post`, `blotato_create_visual`) do not accept local file paths. Media must be a publicly accessible URL. Allowlisting egress to `database.blotato.io` is necessary but not enough on its own. You still upload the file first.

To use a local file:

1. Call `blotato_create_presigned_upload_url` with your filename (include the extension). It returns a `presignedUrl` and a `publicUrl`.
2. Upload the raw file bytes to `presignedUrl` with an HTTP `PUT` (for example `curl -X PUT "<presignedUrl>" --data-binary "@<local_file>"`). Send raw bytes, not JSON and not multipart form data.
3. Pass the returned `publicUrl` in the `mediaUrls` field of `blotato_create_post`.

Do not pass the local file directly to `blotato_create_post`. If you allowlisted `database.blotato.io` but the upload still fails, the file was likely sent directly instead of being PUT to the presigned URL, or the PUT used the wrong body encoding.

***

### Troubleshooting

If the MCP server connects but does not work as expected:

1. **OAuth clients (Claude.ai, Desktop, Cowork, Claude Code):** Make sure you are logged into your Blotato account in the browser. The OAuth approval requires an active Blotato session.
2. **API-key connections (for example, Cursor or a Codex header configuration):** Double check you copy/pasted the correct API key from [Settings > API](https://my.blotato.com/settings/api). Make sure there are no extra spaces or missing characters. If your key ends with one or more `=` characters, copy the full key including the trailing `=`, since dropping it causes a 401 "invalid API key" error.
3. Reload or restart the client after saving its MCP configuration.
   * **OAuth authorization fails:** Check the client-specific tab. If the client supports API-key headers, use that tab’s API-key configuration. Codex uses TOML and header form fields. Cursor uses JSON. Do not paste one client’s configuration into another client.
4. Give your assistant the [agent reference](/start-with-an-ai-agent/llm.md) and ask it to call `blotato_list_accounts`. Reading documentation does not install the MCP server, authorize an account, or expose tools to a chat.
5. If the connector shows "Connected" but a tool fails, inspect the returned error. A subscription-related `403` requires an active paid plan. A `401` points to credentials or session authentication. Follow [authentication checks](#api-guides-authentication). Generating an API key during a trial starts the paid subscription.
6. An empty account list is a successful call. Connect a social account in [Accounts](https://my.blotato.com/accounts) before publishing.
7. For a JSON parsing error, use your editor's local JSON validation. Check for missing commas and unmatched braces. Do not paste configuration containing an API key into an online validator.
8. **Connector shows "Connected" but never authenticates (stale connector session).** If the Blotato connector in Claude Cowork or Claude Desktop shows Connected but every tool call fails with a credential error, and reconnecting does not help, the client is reusing a stale cached session. A simple reconnect reuses that same cached session, so remove it fully and add it fresh:
   1. Go to **Customize > Connectors** and fully remove the Blotato connector
   2. Click the **+** button, choose **Add custom connector**, and approve access again
   3. To double-check the key itself, see [Settings > API](https://my.blotato.com/settings/api)
9. **"Access to this website is blocked by your network egress settings" during a presigned upload.** Claude Cowork and Claude Desktop block outbound network access by default, so the `PUT` to the presigned upload URL cannot reach `database.blotato.io`. To allow it:
   1. Go to **Claude Cowork/Desktop > Settings > Capabilities > Allow network egress**
   2. Set **"Package managers only"**
   3. Under **Additional allowed domains**, add `database.blotato.io` and click **Add**
   4. Fully quit and restart Claude Cowork/Desktop
   5. If your settings look correct but the upload stays blocked, set **Allow network egress** to **"Allow all"** instead of **"Package managers only"**, then restart and retry

Video walkthroughs: [Claude + Blotato Beginner Setup](https://www.youtube.com/watch?v=1dSNfnFL40c) and [Build Your AI Personal Assistant for Social Media Marketing](https://youtu.be/3HVH2Iuplqo)

***

## API access and authentication

API and MCP access is included in paid Starter, Creator, and Agency subscriptions. The free trial does not include API or MCP access. Generating an API key during a trial ends the trial and starts the paid subscription.

### REST API

1. Open [Settings > API](https://my.blotato.com/settings/api).
2. Generate or copy your API key.
3. Include the key in the `blotato-api-key` header of each request.
4. Call `GET https://backend.blotato.com/v2/users/me/accounts` to verify access.

```http
GET /v2/users/me/accounts HTTP/1.1
Host: backend.blotato.com
blotato-api-key: YOUR_API_KEY
```

Copy the full key, including any trailing `=` characters. Preserve the value exactly. JSON values use double quotes. Store credentials in your client's secret settings or environment, outside published examples.

### MCP

The server uses Streamable HTTP at `https://mcp.blotato.com/mcp` and accepts these authentication methods:

| Method         | Configuration                                                  |
| -------------- | -------------------------------------------------------------- |
| OAuth          | Add the server URL and complete the browser authorization flow |
| API key header | Set `blotato-api-key` to your API key                          |
| Bearer header  | Set `Authorization` to `Bearer YOUR_API_KEY`                   |

Use the configuration supported by your client in the [setup guide](#api-mcp-setup). A field named “Bearer token env var” expects an environment variable name, rather than the key itself. For a header-value form, paste the key into the header value.

After connecting, ask the assistant to call `blotato_list_accounts`. An empty array is a successful authenticated response with no connected accounts.

### Resolve access errors

| Result                                      | Next action                                                                                               |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `401` or invalid credentials                | Recopy the full API key, or repeat OAuth authorization in the client                                      |
| `403` with a subscription message           | Check the subscription in [Billing](https://my.blotato.com/settings/billing)                              |
| OAuth fails but an API-key request succeeds | Follow the [OAuth session troubleshooting](/start-with-an-ai-agent/mcp/faqs.md) steps for the client      |
| Accounts are missing                        | Confirm the Blotato account used to authorize, then check [Social accounts](/settings/social-accounts.md) |

Use the returned error to choose the next step. An OAuth error alone does not establish a billing problem.

***

## REST API versus MCP

Both interfaces use your Blotato account and subscription. Choose the interface your assistant or automation uses.

|                                      | MCP                                                                          | REST API                                                                   |
| ------------------------------------ | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Use from                             | An assistant with MCP tools                                                  | HTTP code, n8n, Make, or other workflow tools                              |
| Connect to                           | `https://mcp.blotato.com/mcp`                                                | `https://backend.blotato.com/v2`                                           |
| Authenticate                         | OAuth or an API key, depending on the client                                 | `blotato-api-key` header                                                   |
| Publish input                        | Tool arguments such as `accountId`, `platform`, `text`, and `mediaUrls`      | A JSON body containing `post.accountId`, `post.content`, and `post.target` |
| Account lookup                       | `blotato_list_accounts` includes subaccounts                                 | Fetch accounts, then fetch subaccounts when needed                         |
| Immediate post and source completion | Tools wait internally for up to 20 seconds, then return the available result | Submit, save the returned ID, and poll the status endpoint                 |
| Visual completion                    | Call the visual status tool                                                  | Poll the visual status endpoint                                            |
| Reference                            | [MCP tools](#api-mcp-tools)                                                  | [OpenAPI](/rest-api-reference/openapi-reference.md)                        |

MCP tools expose a selected set of workflows. An endpoint in OpenAPI does not imply a corresponding MCP tool. For example, REST includes schedule-slot management and a [webhook publishing target](#api-api-reference-publish-post--webhook). MCP manages scheduled posts and publishes to the 9 listed social platforms, but does not expose these 2 REST capabilities.

### Choose your setup

1. If your assistant already has `blotato_` tools, call those tools directly using their schemas.
2. If you are adding Blotato to an assistant, follow [MCP setup](#api-mcp-setup).
3. If you are building HTTP requests, follow the [REST quickstart](#api-start).
4. If you use workflow software, follow the [n8n](/integrations-and-automation-templates/n8n.md) or [Make](/integrations-and-automation-templates/make.com.md) guide.

Keep the 2 request shapes separate. Copying a REST `post` object into an MCP tool call fails when the tool expects flat arguments.

***

## MCP input index

This field index is verified against the 36 tool registrations in Blotato's MCP server. Use the connected tool schema for types, nested objects, allowed values, and defaults.

Required below means required by the tool-level input schema. Platform-specific conditions still apply. For example, Facebook needs a page ID and TikTok needs its privacy and disclosure fields. Follow the [platform guides](/platform-publishing/platforms.md).

These are MCP arguments, not REST bodies. Read [REST versus MCP](#api-guides-rest-vs-mcp) before translating a request.

| Tool                                                                                         | Required arguments                             | Optional arguments                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| -------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`blotato_get_user`](#api-mcp-tools--blotato_get_user)                                       | —                                              | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_accounts`](#api-mcp-tools--blotato_list_accounts)                             | —                                              | `platform`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| [`blotato_list_pinterest_boards`](#api-mcp-tools--blotato_list_pinterest_boards)             | `accountId`                                    | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_create_automation`](#api-mcp-tools--blotato_create_automation)                     | `accountId`, `platform`, `name`, `dmMessage`   | `pageId`, `triggers`, `buttons`, `followGate`, `emailGate`, `webhook`, `isActive`                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_automations`](#api-mcp-tools--blotato_list_automations)                       | —                                              | `limit`, `cursor`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_automation_runs`](#api-mcp-tools--blotato_list_automation_runs)               | `automationId`                                 | `limit`, `cursor`, `status`, `since`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| [`blotato_list_automation_logs`](#api-mcp-tools--blotato_list_automation_logs)               | `automationId`                                 | `flowRunId`, `limit`, `cursor`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| [`blotato_get_automation_analytics`](#api-mcp-tools--blotato_get_automation_analytics)       | `automationId`                                 | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_update_automation`](#api-mcp-tools--blotato_update_automation)                     | `automationId`                                 | `name`, `triggers`, `dmMessage`, `buttons`, `followGate`, `emailGate`, `webhook`, `isActive`                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| [`blotato_update_automation_trigger`](#api-mcp-tools--blotato_update_automation_trigger)     | `automationId`, `triggerId`, `isActive`        | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_delete_automation`](#api-mcp-tools--blotato_delete_automation)                     | `automationId`                                 | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_comments`](#api-mcp-tools--blotato_list_comments)                             | —                                              | `limit`, `cursor`, `platform`, `accountId`, `postId`, `parentCommentId`, `since`, `until`                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| [`blotato_get_comment`](#api-mcp-tools--blotato_get_comment)                                 | `commentId`                                    | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_post_comment`](#api-mcp-tools--blotato_post_comment)                               | `postId`, `text`                               | `parentCommentId`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_get_credits`](#api-mcp-tools--blotato_get_credits)                                 | —                                              | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_create_presigned_upload_url`](#api-mcp-tools--blotato_create_presigned_upload_url) | `filename`                                     | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_conversations`](#api-mcp-tools--blotato_list_conversations)                   | —                                              | `limit`, `cursor`, `platform`, `accountId`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| [`blotato_get_conversation`](#api-mcp-tools--blotato_get_conversation)                       | `conversationId`                               | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_messages`](#api-mcp-tools--blotato_list_messages)                             | —                                              | `conversationId`, `limit`, `cursor`, `platform`, `accountId`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| [`blotato_get_message`](#api-mcp-tools--blotato_get_message)                                 | `messageId`                                    | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_send_message`](#api-mcp-tools--blotato_send_message)                               | `accountId`, `platform`, `recipientId`, `text` | `pageId`, `commentId`, `buttons`, `quickReplies`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| [`blotato_create_post`](#api-mcp-tools--blotato_create_post)                                 | `accountId`, `platform`, `text`                | `mediaUrls`, `additionalPosts`, `pageId`, `mediaType`, `link`, `replyControl`, `privacyLevel`, `disabledComments`, `disabledDuet`, `disabledStitch`, `isBrandedContent`, `isYourBrand`, `isAiGenerated`, `autoAddMusic`, `isDraft`, `imageCoverIndex`, `videoCoverTimestamp`, `boardId`, `title`, `altText`, `collaborators`, `coverImageUrl`, `shareToFeed`, `audioName`, `firstComment`, `trial`, `privacyStatus`, `shouldNotifySubscribers`, `isMadeForKids`, `containsSyntheticMedia`, `thumbnailUrl`, `playlistIds`, `scheduledTime`, `useNextFreeSlot` |
| [`blotato_get_post_status`](#api-mcp-tools--blotato_get_post_status)                         | `postSubmissionId`                             | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_posts`](#api-mcp-tools--blotato_list_posts)                                   | —                                              | `since`, `until`, `limit`, `cursor`, `status`, `platform`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| [`blotato_list_schedules`](#api-mcp-tools--blotato_list_schedules)                           | —                                              | `limit`, `cursor`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_get_schedule`](#api-mcp-tools--blotato_get_schedule)                               | `id`                                           | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_update_schedule`](#api-mcp-tools--blotato_update_schedule)                         | `id`                                           | `scheduledTime`, `post`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| [`blotato_delete_schedule`](#api-mcp-tools--blotato_delete_schedule)                         | `id`                                           | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_create_source`](#api-mcp-tools--blotato_create_source)                             | `sourceType`                                   | `url`, `text`, `customInstructions`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| [`blotato_get_source_status`](#api-mcp-tools--blotato_get_source_status)                     | `id`                                           | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_visual_templates`](#api-mcp-tools--blotato_list_visual_templates)             | —                                              | `search`, `id`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| [`blotato_create_visual`](#api-mcp-tools--blotato_create_visual)                             | `templateId`                                   | `inputs`, `prompt`, `render`, `title`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| [`blotato_get_visual_status`](#api-mcp-tools--blotato_get_visual_status)                     | `id`                                           | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| [`blotato_list_top_posts`](#api-mcp-tools--blotato_list_top_posts)                           | —                                              | `since`, `until`, `platform`, `sortBy`, `limit`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| [`blotato_get_post_analytics`](#api-mcp-tools--blotato_get_post_analytics)                   | `id`                                           | —                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |

For results, workflow order, and recovery, use the [tool reference](#api-mcp-tools) and [agent workflows](/agent-workflows/agent-workflows.md).

***

## Tools Reference

The Blotato MCP Server exposes 36 tools. Use the connected tool's input schema for its arguments. Start with [client setup](#api-mcp-setup) or choose an [agent workflow](/agent-workflows/agent-workflows.md).

Call `blotato_list_accounts` before publishing. Follow [completion rules](#api-guides-async-jobs-and-polling) after submitting a post, visual, source, comment, or message. REST JSON bodies and MCP tool arguments have [different shapes](#api-guides-rest-vs-mcp).

### Accounts

For every tool's required and optional argument names, see the [MCP input index](#api-mcp-inputs). The sections below explain behavior and results.

#### blotato\_get\_user

Get your account info and verify the connection is working.

* **Input**: none
* **Output**: user ID, email, subscription status

#### blotato\_list\_accounts

List all connected social media accounts with subaccounts (for Facebook Pages and LinkedIn Company Pages).

* **Input**: `platform` (optional) - filter by platform name
* **Output**: array of accounts with ID, platform, name, username, and subaccounts

#### blotato\_list\_pinterest\_boards

List boards owned by a connected Pinterest account. Use this to get the `boardId` required when publishing a Pinterest pin.

* **Input**: `accountId` (required) - Pinterest account ID from blotato\_list\_accounts
* **Output**: array of boards with `id` and `name`. Use `id` as `boardId` in blotato\_create\_post.

***

### Credits

#### blotato\_get\_credits

Get the account's remaining credits, the account email the API key belongs to, and current pricing (price per 1,000 credits and the min/max purchase quantity). If the user wants more credits, state the returned account email, then direct them to [Settings > Billing](https://my.blotato.com/settings/billing). Credits are non-transferable between accounts.

* **Output**: `creditsRemaining`, `accountEmail`, `purchaseQuantityRange` (`min` and `max`), and `pricePer1000CreditsUsd`.

### Publishing

#### blotato\_create\_post

Create and publish (or schedule) a post to a social media platform.

* **Input**:
  * `accountId` (required) - from blotato\_list\_accounts
  * `platform` (required) - twitter, instagram, facebook, tiktok, linkedin, pinterest, bluesky, threads, or youtube
  * `text` (required) - post content
  * `mediaUrls` (optional) - array of public media URLs
  * `scheduledTime` (optional) - ISO 8601 datetime
  * `useNextFreeSlot` (optional) - use next available schedule slot
  * `pageId` (optional) - for Facebook/LinkedIn pages
  * `mediaType` (optional) - for Instagram/Facebook: reel or story. Facebook videos must be `"reel"` (regular feed videos no longer supported).
  * `trial` (optional) - for Instagram reels: object with `graduationStrategy` (`"MANUAL"` or `"SS_PERFORMANCE"`). Trial reels are shown to non-followers first.
  * `firstComment` (optional) - for Facebook/Instagram: text auto-posted as the first comment right after publishing. Not supported for stories. Useful for links.
  * `privacyLevel` (optional) - for TikTok
  * `additionalPosts` (optional) - array of additional posts for threads (Twitter, Bluesky, Threads). Each entry has `text` and `mediaUrls`. The first post uses the top-level `text` and `mediaUrls` fields. Blotato handles reply chaining.
  * `boardId` (optional) - for Pinterest (get from blotato\_list\_pinterest\_boards)
  * `title` (optional) - for Pinterest or YouTube
  * `privacyStatus` (optional) - for YouTube: public, private, or unlisted
  * `playlistIds` (optional) - for YouTube: array of playlist IDs from blotato\_list\_accounts to add the video to playlists
  * `thumbnailUrl` (optional) - for YouTube: publicly accessible image URL for a custom thumbnail. Requires a verified YouTube account in good standing
* **Output**: postSubmissionId (poll with blotato\_get\_post\_status)

#### blotato\_get\_post\_status

Check the status of a submitted post.

* **Input**: `postSubmissionId` (required)
* **Output**: status, publicUrl (when available on a published result), errorMessage (when failed)
* **Status values**: in-progress -> published | scheduled | failed

`in-progress` also covers a lookup with no matching result record. It does not validate a guessed ID or confirm a live queue job. Use the original submission ID, keep a polling limit, and follow [post lifecycle](#api-guides-post-lifecycle).

#### blotato\_list\_posts

List the user's posts (scheduled, published, and failed) within a time window, ordered by post time (most recent first). Supports cursor-based pagination and optional filters by status and platform.

Each item includes an `account` field and a `state` field with a `type` of `scheduled`, `published` (includes `postUrl` and `analytics`), or `failed` (includes `errorMessage`). `account` is null when the connected account no longer exists. Published-post analytics are null until metrics have been collected.

* **Input**:
  * `since` (optional) - ISO 8601 timestamp. Defaults to 7 days ago.
  * `until` (optional) - ISO 8601 timestamp. Defaults to 7 days from now.
  * `limit` (optional) - number of posts per page. Min: 1, Max: 250. Default: 50
  * `cursor` (optional) - pagination cursor from a previous response
  * `status` (optional) - array of statuses to include: `scheduled`, `published`, `failed`. Omit to include all.
  * `platform` (optional) - array of platforms to include: twitter, instagram, linkedin, facebook, tiktok, pinterest, threads, bluesky, youtube. Omit to include all.
* **Output**: array of posts with `id`, `postTime`, `platform`, `text`, `mediaUrls`, `account`, and a `state` object. A published state includes `analytics.latest` and `analytics.history`, or `analytics: null`. Includes `cursor` for the next page when more pages are available.

***

### Analytics

#### blotato\_list\_top\_posts

List your top performing published posts, ranked by an engagement metric over a time range. Use this to answer questions like "what were my best posts last month" or "which posts got the most views".

Each item includes the post content, public URL, platform, publish time, media URLs, the latest analytics snapshot, and the full snapshot history. Analytics are refreshed periodically in the background, so recent posts may have no metrics yet. Metric values are returned as strings because counts can exceed normal number precision.

* **Input**:
  * `since` (optional) - ISO 8601 timestamp. Only include posts published on or after this time. Defaults to 30 days ago.
  * `until` (optional) - ISO 8601 timestamp. Only include posts published on or before this time. Defaults to now.
  * `platform` (optional) - the MCP tool accepts `twitter`, `instagram`, `facebook`, `threads`, or `bluesky` as an explicit filter. Omit to include all available results. The [REST analytics endpoint](#api-analytics) also accepts TikTok, YouTube, and Pinterest filters. The MCP filter list is narrower than the backend's collection coverage.
  * `sortBy` (optional) - metric to rank by: `likes_count`, `comments_count`, `views_count`, or `reach_count`. Defaults to `views_count`. For Twitter/X, comments ranking uses `replies_count` when comments are not reported, and views ranking uses `impressions_count` when views are not reported.
  * `limit` (optional) - number of posts to return. Min: 1, Max: 100. Default: 20
* **Output**: array of `items`, each with `id`, `content`, `postUrl`, `platform`, `createdAt`, `mediaUrls`, `latestMetrics`, and `metricsHistory`.

#### blotato\_get\_post\_analytics

Get analytics for a single published post, including the latest metrics and the full snapshot history. This does not trigger a re-fetch; it returns the most recent analytics collected in the background.

Use blotato\_list\_posts or blotato\_list\_top\_posts to find the published post id first.

* **Input**: `id` (required) - published post id from blotato\_list\_posts or blotato\_list\_top\_posts
* **Output**: `publishedPostId`, `platform`, `lastFetchedAt`, `lastError`, `metrics` (latest, or null when not synced yet), and `history` (array of snapshots, each with `fetchedAt` and `metrics`).

### Comments

Read and post comments on your published Instagram and Facebook posts, both audience replies and comments you post through Blotato.

#### blotato\_list\_comments

List comments on your published posts, ordered by creation time (most recent first). Supports cursor-based pagination and optional filters.

Each comment includes an `isAuthor` flag: `true` for comments you posted, `false` for audience replies.

* **Input**:
  * `limit` (optional) - number of comments per page. Min: 1, Max: 250. Default: 50
  * `cursor` (optional) - pagination cursor from a previous response
  * `platform` (optional) - filter by platform: `instagram` or `facebook`
  * `accountId` (optional) - filter to a single connected account
  * `parentCommentId` (optional) - filter to direct replies to a single comment
  * `postId` (optional) - filter to comments on a single Blotato-published post (from blotato\_list\_posts)
  * `since` (optional) - only include comments created on or after this ISO 8601 timestamp
  * `until` (optional) - only include comments created on or before this ISO 8601 timestamp
* **Output**: array of comments with `id`, `text`, `status`, `isAuthor`, `platform`, `postId`, `parentCommentId`, `createdAt`, and `errorMessage` (when failed). Includes `cursor` for the next page when present.

#### blotato\_get\_comment

Get a single comment by its Blotato ID.

* **Input**: `commentId` (required) - comment ID from blotato\_list\_comments
* **Output**: `id`, `text`, `status`, `isAuthor`, `platform`, `postId`, `createdAt`, and `errorMessage` (when failed)

#### blotato\_post\_comment

Post a comment on one of your published Instagram or Facebook posts, or a reply to an existing top-level comment when you pass `parentCommentId`. Blotato queues the comment and posts it in the background. To check the status of the comment, poll with blotato\_get\_comment. Replies to audience comments count toward your monthly active-contacts limit.

* **Input**:
  * `postId` (required) - Blotato ID of the published post (from blotato\_list\_posts)
  * `text` (required) - comment body. Instagram allows up to 2200 characters, Facebook up to 8000
  * `parentCommentId` (optional) - Blotato ID of a top-level, posted comment on the same post to reply to. Replies to replies are not supported
* **Output**: the queued comment (poll with blotato\_get\_comment)
* **Status values**: queued -> processing -> posted | failed

***

### Messages

Read and send direct messages on your Instagram account and Facebook Page. Reply to people who already have a conversation with you.

#### blotato\_list\_conversations

List your direct-message conversations, ordered by most recent activity. Supports cursor-based pagination and optional filters.

* **Input**:
  * `limit` (optional) - number of conversations per page. Min: 1, Max: 250. Default: 50
  * `cursor` (optional) - pagination cursor from a previous response
  * `platform` (optional) - filter by platform: `instagram` or `facebook`
  * `accountId` (optional) - filter to a single connected account
* **Output**: array of conversations with `id`, `platform`, `participants`, `updatedAt`, and `createdAt`. Includes `cursor` for the next page when present.

#### blotato\_get\_conversation

Get a single conversation by its Blotato ID.

* **Input**: `conversationId` (required) - conversation ID from blotato\_list\_conversations
* **Output**: conversation object with `id`, `platform`, `participants`, and `updatedAt`

#### blotato\_list\_messages

List your messages, ordered by creation time (most recent first). Supports cursor-based pagination and optional filters.

* **Input**:
  * `limit` (optional) - number of messages per page. Min: 1, Max: 250. Default: 50
  * `cursor` (optional) - pagination cursor from a previous response
  * `conversationId` (optional) - filter to a single conversation
  * `platform` (optional) - filter by platform: `instagram` or `facebook`
  * `accountId` (optional) - filter to a single connected account
* **Output**: array of messages with `id`, `conversationId`, `direction`, `text`, `payload`, `status`, `senderId`, `recipientId`, and `createdAt`. `direction` is `incoming` for messages you receive and `outgoing` for messages you send. Includes `cursor` for the next page when present.

#### blotato\_get\_message

Get a single message by its Blotato ID. Poll this after blotato\_send\_message to check whether an outgoing message reached `sent` or `failed`.

* **Input**: `messageId` (required) - message ID from blotato\_list\_messages
* **Output**: message object with `id`, `conversationId`, `direction`, `text`, `payload`, `status`, `senderId`, `recipientId`, and `createdAt`

#### blotato\_send\_message

Send a direct message to one recipient, or a private reply to a comment. Blotato queues the message and sends it in the background. To check the status, poll with blotato\_get\_message.

* **Input**:
  * `accountId` (required) - connected account to send from (from blotato\_list\_accounts)
  * `platform` (required) - `instagram` or `facebook`
  * `pageId` (required for Facebook) - the Facebook Page to send from (from the account's subaccounts via blotato\_list\_accounts)
  * `recipientId` (required) - the other party's id. Use an incoming message's `senderId` (from blotato\_list\_messages)
  * `text` (required) - message body. Instagram messages are limited to 1000 bytes, Facebook to 2000 characters. With `buttons` set, the limit drops to 640 characters
  * `commentId` (optional) - to send a private reply to a comment instead of a direct message, pass the comment's Blotato ID
  * `buttons` (optional) - Facebook and Instagram only. 1 to 3 call-to-action buttons pinned under the message. See [Buttons and Quick Replies](#api-mcp-tools--buttons-and-quick-replies)
  * `quickReplies` (optional) - Facebook and Instagram only. 1 to 13 tappable reply chips. See [Buttons and Quick Replies](#api-mcp-tools--buttons-and-quick-replies)
* **Output**: the queued message (poll with blotato\_get\_message)
* **Status values**: queued -> processing -> sent | failed

#### Buttons and Quick Replies

Facebook and Instagram messages may be sent with attached buttons or quick replies via `buttons` or `quickReplies`. They are mutually exclusive - pass one or the other, never both.

**Buttons** sit under the message and stay there. Each has:

* `type` - `web_url` opens a link, `postback` reports the tap back to you
* `title` - button label, 1 to 20 characters
* `url` - required for `web_url`, starts with `https://`
* `payload` - required for `postback`, 1 to 1000 characters

Blotato supports `web_url` and `postback` buttons. Phone buttons are not supported.

**Instagram renders buttons in the mobile app only.** A recipient reading the conversation on instagram.com in a desktop browser sees the message text with no buttons. Pass `buttons` or a URL inside `text`, not both, since a message carrying both leaves the URL unclickable on desktop. For a desktop audience, drop `buttons` and put the URL in `text`.

**Quick replies** are tappable chips offered as canned answers. They disappear once the person taps one or types their own message. Each has a `title` (1 to 20 characters) and a `payload` (1 to 1000 characters).

**Reading a tap.** Both kinds of tap arrive as the next incoming message carrying `payload.selection`, readable with blotato\_list\_messages. A quick-reply tap sends a real text message, so `text` holds the chip label. A postback sends no text message: the tap is recorded as an incoming message with `text` set to the button label. Either way, branch on `selection.payload` rather than `text`, since two chips sharing a label are distinguishable only by payload. A `web_url` button produces no selection.

**Reconnect for postback buttons.** Blotato subscribes your account to the postback webhook when you connect it. If you connected your account before buttons were launched, [reconnect](https://my.blotato.com/accounts) to start receiving postback events. Quick-reply taps need no reconnect.

See [Buttons and Quick Replies](#api-api-reference-messages--buttons-and-quick-replies) for the REST equivalent.

***

### Automations

Create and manage DM automations on Instagram and Facebook Pages. An automation sends one direct message when someone comments on your post or sends your account a message. See [DM Automations](/web-app-features/dm-automations.md).

An automation sends one message by default. Three optional steps extend it, and Blotato runs them in a fixed order: a follow gate (Instagram only), an email gate, your message, then a webhook.

Every live automation whose trigger matches starts its own run. Two automations matching the same comment both try to answer it, and only one reply reaches the person. Keep one live automation per keyword on each account. Messages without text, such as photos or stickers, never start a run.

Every DM an automation sends counts toward your plan's monthly active-contacts limit. Gate messages go to the same person, so one contact still counts once. See [Active Contacts](/settings/billing-and-credits.md#active-contacts).

#### blotato\_create\_automation

Create a DM automation. It starts as an inactive draft unless `isActive` is true. You can add an email gate, an Instagram follow gate, or a webhook.

* **Input**:
  * `accountId` (required) - Instagram or Facebook account from blotato\_list\_accounts
  * `platform` (required) - `instagram` or `facebook`
  * `pageId` (required for Facebook) - the Facebook Page from the account's subaccounts
  * `name` (required) - automation label, 1 to 60 characters
  * `triggers` (optional) - up to 2 triggers, at most one `comment-received` and one `message-received`. Each has `type`, `keywords` (omit or empty to match any comment or text message), `postId` (comment triggers only, the tracked Blotato post ID, not the native platform ID), and `isActive` (default true). For an externally published Instagram post already tracked through an incoming comment, use the comment's non-null `postId` after verifying the account and `platformPostId`. Omitting `postId` matches any post and requires approval for this scope. See [post targeting](#api-api-reference-dm-automations--trigger-object). Any one trigger firing starts a single run. Omit `triggers` for a draft with no triggers
  * `dmMessage` (required) - the message sent when a trigger fires, 1 to 640 characters
  * `buttons` (optional) - up to 3 link buttons, each with `type: "url"`, a `title` of 20 characters or fewer, and a `url`
  * `followGate` (optional, Instagram only) - `message` (1 to 640 characters) and `buttonTitle` (20 characters or fewer, default `I'm following`). Holds the DM back until the contact follows the account. Rejected on a Facebook automation
  * `emailGate` (optional) - `message` (1 to 640 characters). Holds the DM back until the contact replies with an email address. On automations published after September 14, 2026, a contact with an email already on file skips it
  * `webhook` (optional) - `method` (`GET`, `POST`, `PUT`, `PATCH`, or `DELETE`), `url` (public `http(s)`, 2048 characters or fewer), and `headers` (optional). Called once the DM sends
  * `isActive` (optional) - true publishes it immediately. Default false
* **Output**: the created automation

**Follow gate.** Blotato sends the gate message with a confirm button, waits up to 30 days for a reply, then reads follower status. A contact who does not follow gets the gate message again. An unknown status sends the DM anyway, since Instagram withholds follower status for a contact who never granted profile access. A confirmed follow is reused for 30 days, but the gate message still goes out on every run.

Any text reply advances the follow gate, so the button tap is a shortcut rather than a requirement. Two setup points to pass on to the user. The Instagram account needs reconnecting if they connected it before DM automations and follow gating landed, since Blotato subscribes to button-tap events at connect time and an older connection does not reliably receive them, leaving runs to expire. Instagram also renders the confirm button in the mobile app only, so write the gate `message` to ask for a typed reply such as "Reply FOLLOWING once you have" in the message text.

**Email gate.** Blotato sends the gate message, waits up to 72 hours, and reads the first email address out of the reply. A reply holding no address returns the gate message. A match saves to the contact, then the DM sends. A contact with an email already on file skips the gate, and the DM sends with no gate message. This applies to automations published after September 14, 2026. An automation published earlier asks every time until you pass its current `triggers` to blotato\_update\_automation. Use a webhook to export the stored contact email. Conversation and message responses have no dedicated captured-email field, although the original reply remains available as message text.

**Webhook.** Every method except `GET` carries a JSON body with two keys, with or without a gate on the automation: `{"email": "them@example.com", "isFollower": true}`. `email` is the address on file for the contact, captured by an email gate, and `null` when Blotato holds none. `isFollower` is the follower status a follow gate last recorded, and `null` when Blotato never checked. `GET` carries no body. The host must resolve to a public address, redirects are not followed, and the timeout is 15 seconds. A non-2xx response or a timeout gets logged and the run still completes, with no retry. A blocked address, a DNS failure, or a connection error fails the run with error code 20304.

#### blotato\_list\_automations

List your DM automations, ordered by creation time (most recent first). Supports cursor-based pagination and selected analytics metrics.

* **Input**:
  * `limit` (optional) - number of automations per page. Min: 1, Max: 250. Default: 50
  * `cursor` (optional) - pagination cursor from a previous response
* **Output**: array of automations with `id`, `accountId`, `name`, `platform`, `target`, `triggers` (each with `id`, `type`, `keywords`, `postId`, and `isActive`), `dmMessage`, `buttons`, `emailGate`, `followGate`, `webhook`, `isActive`, `publishedVersionId`, `createdAt`, `updatedAt`, and `stats`. `stats` contains `triggered`, `completed`, and `failed` counts from the last 24 hours plus `lastRunAt`, which is null when the automation has never run. Archived automations do not appear. Includes `cursor` for the next page when present.

#### blotato\_update\_automation

Update a DM automation. Any of `name`, `triggers`, `dmMessage`, `buttons`, `followGate`, `emailGate`, or `webhook` you pass replaces that field. Omitted fields stay unchanged. Pass `null` to remove a gate or a webhook. `triggers` replaces the whole trigger set, and `[]` removes every trigger.

Content changes to a live automation publish immediately. Changes to a draft stay saved until you pass `isActive` true. A live automation needs at least one active trigger, so to clear every trigger pass an empty `triggers` array and `isActive` false in the same call. To remove one trigger, pass `triggers` holding the trigger you keep. To update a single trigger without other changes, use blotato\_update\_automation\_trigger.

* **Input**:
  * `automationId` (required) - automation ID from blotato\_list\_automations
  * `name` (optional) - new automation label, 1 to 60 characters
  * `triggers` (optional) - replaces every trigger. Up to 2, at most one per type. `[]` removes all
  * `dmMessage` (optional) - replaces the message text
  * `buttons` (optional) - replaces the link buttons
  * `followGate` (optional, Instagram only) - replaces the follow gate. Pass null to remove it
  * `emailGate` (optional) - replaces the email gate. Pass null to remove it
  * `webhook` (optional) - replaces the webhook. Pass null to remove it
  * `isActive` (optional) - true publishes, false moves to draft. Omit to keep the current state
* **Output**: the updated automation

#### blotato\_update\_automation\_trigger

Update a single trigger on a DM automation, for example to turn it on or off. The change applies immediately on a live automation, with no republish. At least one trigger must stay on while the automation is live. To pause the whole automation, pass `isActive` false to blotato\_update\_automation instead.

Trigger IDs change whenever the automation's content or triggers are updated. Read them from blotato\_list\_automations or from the automation blotato\_update\_automation returns.

* **Input**:
  * `automationId` (required) - automation ID from blotato\_list\_automations
  * `triggerId` (required) - trigger ID from the automation's `triggers`
  * `isActive` (required) - true turns the trigger on, false turns it off
* **Output**: the updated trigger

#### blotato\_delete\_automation

Archive a DM automation and stop new runs. Existing runs, including waiting gates, remain able to continue. Archiving does not cancel messages already queued. Archive only when requested. The archived automation no longer appears in blotato\_list\_automations.

* **Input**: `automationId` (required) - automation ID from blotato\_list\_automations
* **Output**: the archived automation

#### blotato\_list\_automation\_runs

List the execution runs of one automation, ordered by start time (most recent first). A run is one execution: one comment or message matched a trigger. Use it to check whether an automation fires and why a reply did not go out.

* **Input**:
  * `automationId` (required) - automation ID from blotato\_list\_automations
  * `limit` (optional) - number of runs per page. Min: 1, Max: 250. Default: 50
  * `cursor` (optional) - pagination cursor from a previous response
  * `status` (optional) - array of statuses to include: `running`, `waiting`, `completed`, `expired`, `superseded`, `failed`. Omit to include all
  * `since` (optional) - ISO 8601 timestamp. Only runs with activity at or after this time are returned
* **Output**: array of runs with `id`, `contactId`, `platform`, `status`, `error` (when failed), `createdAt`, and `updatedAt`. A failed run's `error` includes `code`, `message`, and, when available, `action` and `details` with recovery context
* **Status values**: running, waiting, completed, expired, superseded, failed. A run expires when the contact never answers a gate inside its window: 30 days for a follow gate, 72 hours for an email gate. A run also expires when its message never settles within 30 minutes. A run is superseded when a newer run starts waiting on the same contact

#### blotato\_list\_automation\_logs

List the execution logs written as an automation's runs advance, ordered by creation time (most recent first).

* **Input**:
  * `automationId` (required) - automation ID from blotato\_list\_automations
  * `flowRunId` (optional) - filter to a single run, from blotato\_list\_automation\_runs
  * `limit` (optional) - number of logs per page. Min: 1, Max: 250. Default: 50
  * `cursor` (optional) - pagination cursor from a previous response
* **Output**: array of logs with `id`, `flowRunId`, `nodeId`, `level` (`info`, `warning`, `error`), `message`, `context`, and `createdAt`

#### blotato\_get\_automation\_analytics

Get analytics for one automation. `triggered`, `completed`, and `failed` are all-time totals. `issues` counts runs that failed during the last 7 days and is a subset of `failed`. A run ending on an error after the DM sends counts as failed. Runs still in flight, and runs ending as expired or superseded, count toward `triggered` but not toward `completed` or `failed`.

* **Input**: `automationId` (required) - automation ID from blotato\_list\_automations
* **Output**: `triggered`, `completed`, `failed`, `issues`

***

### Content Extraction

#### blotato\_create\_source

Extract content from a URL or text. Polls internally for up to 20 seconds and returns the result directly. If still processing (e.g., a long YouTube video), returns the source ID -- use blotato\_get\_source\_status to poll for completion.

* **Input**:
  * `sourceType` (required) - youtube, article, twitter, tiktok, text, audio, pdf, or perplexity-query
  * `url` (optional) - required for URL-based source types
  * `text` (optional) - required for text and perplexity-query source types
  * `customInstructions` (optional) - guide extraction (e.g., "focus on key takeaways", "summarize in 5 bullet points")
* **Output**: `id` and status. A completed result includes `title`, `content`, and `referenceUrl` when available. Extraction failure from this create tool is an MCP error containing the source ID.

Long source content ends with `[output truncated]` when it exceeds the MCP response budget. A `completed` job does not prove the tool returned the full text. For complete content, use an authorized REST lookup with the same ID. Repeating the MCP status call does not paginate the text. See [Extract and repurpose content](#api-agent-workflows-sources).

#### blotato\_get\_source\_status

Check the status of a source extraction. Use this if blotato\_create\_source timed out and returned an in-progress status.

* **Input**: `id` (required) - source ID from blotato\_create\_source
* **Output**: `id`, status, `title` and `content` when completed, `message` when failed. Completed content is subject to the same truncation behavior as the create tool.
* **Status values**: queued -> processing -> completed | failed

An extraction `failed` result from this status tool is returned in JSON without an MCP `isError` flag. Inspect `status` as well as the tool error flag.

***

### Videos and Images

#### blotato\_list\_visual\_templates

List all available visual templates (videos, carousels, quote cards, infographics).

* **Input**: `search` (optional) - case-insensitive literal text search in template name or description. Regex characters are matched literally. `id` (optional) - retrieve the exact template ID instead of searching.
* **Output**: `items` containing `id`, `description`, and available `inputs`. Do not depend on a `title` or `name` field in the MCP result. For names through REST, request `fields=id,name,description,inputs`.

Inspect the returned input specification before generation. A template lookup does not create a visual or require a blank generation request. See [Inspect inputs without generating](#api-api-reference-create-video--inspect-inputs-without-generating-a-visual).

#### blotato\_create\_visual

Generate an image, carousel, or video from a template.

* **Input**:
  * `templateId` (required) - from blotato\_list\_visual\_templates
  * `inputs` (optional) - template-specific values. Use `{}` with a prompt for a first request.
  * `prompt` (optional) - describe what you want in natural language
  * `render` (optional, default: true)
  * `title` (optional) - title for the visual
* **Output**: `item.id` and `item.status` (poll with blotato\_get\_visual\_status)

#### blotato\_get\_visual\_status

Check the status of a visual generation request.

* **Input**: `id` (required)
* **Output**: `item.status`, `item.mediaUrl` (videos), `item.imageUrls` (images/carousels), and `item.error` when generation fails
* **Status values**: `queueing`, `generating-script`, `script-ready`, `generating-media`, `media-ready`, `exporting`, `done`, `creation-from-template-failed`, `insufficient-credits`, or `draft`
* Stop polling when `item.error` is set, including on an intermediate status. Use media URLs after `item.status` reaches `done`. Wait at least 15 seconds between status calls.

***

### Content Calendar

#### blotato\_list\_schedules

List all future scheduled posts, ordered by scheduled time (ascending). Supports cursor-based pagination.

Each schedule includes the draft content (same structure as the `post` object in blotato\_create\_post), the scheduled time in UTC, and the target account information.

* **Input**:
  * `limit` (optional) - number of schedules per page. Min: 1, Max: 50. Default: 20
  * `cursor` (optional) - pagination cursor from a previous response
* **Output**: array of schedules with ID, scheduledAt, account info, and draft content. Includes `count` (total) and `cursor` (for next page, if present).

#### blotato\_get\_schedule

Get a single scheduled post by ID. Returns the full schedule details including draft content, scheduled time, and account info.

* **Input**: `id` (required) - schedule ID from blotato\_list\_schedules
* **Output**: schedule object with ID, scheduledAt, account, and draft

#### blotato\_update\_schedule

Update a scheduled post's content, scheduled time, or both. At least one field is required.

The scheduled time must be a valid ISO 8601 date string in the future. When the time changes, the post is re-queued for publishing at the new time.

To update the post content, provide the same fields as blotato\_create\_post (accountId, platform, text, mediaUrls, and platform-specific fields). Send the full post object -- partial updates are not supported.

* **Input**:
  * `id` (required) - schedule ID from blotato\_list\_schedules
  * `scheduledTime` (optional) - new ISO 8601 timestamp, must be in the future
  * `post` (optional) - updated post object with accountId, platform, text, mediaUrls, and platform-specific fields
* **Output**: confirmation message

#### blotato\_delete\_schedule

Delete a scheduled post and cancel its publishing job. This action cannot be undone.

* **Input**: `id` (required) - schedule ID from blotato\_list\_schedules
* **Output**: confirmation message

***

### Media

#### blotato\_create\_presigned\_upload\_url

Get a presigned URL to upload a local file directly to Blotato. No intermediate storage (Google Drive, S3) required.

* **Input**: `filename` (required) - filename with extension, used to determine content type (e.g., "photo.jpg", "video.mp4")
* **Output**: `presignedUrl` (URL to PUT the file to, expires after a short period) and `publicUrl` (the final public URL to use in blotato\_create\_post)
* **Max file size**: depends on your plan (see [Plan Limits](/settings/billing-and-credits.md#plan-limits))
* **Rate limit**: 120 requests per minute

**How to use:**

1. Call `blotato_create_presigned_upload_url` with your filename
2. PUT the file contents to the returned `presignedUrl` with the correct Content-Type header
3. Use the returned `publicUrl` in blotato\_create\_post's `mediaUrls` field

If your media is already at a public URL, you don't need to upload it first -- pass the URL directly into blotato\_create\_post's `mediaUrls` field. The presigned upload tool is for local files that don't have a public URL yet.

***

## Accounts and identifiers

Get IDs from Blotato responses. A username, profile URL, or native platform post ID does not replace a Blotato ID.

### Select an account

1. Call `blotato_list_accounts` through MCP, or `GET /v2/users/me/accounts` through REST.
2. Match the returned platform and account name to the user's intended destination.
3. Use the returned account `id` as `accountId`.
4. Select any required Page, board, or playlist from the lookups below.

If several accounts match, ask which account to use. If none match, direct the user to [Accounts](https://my.blotato.com/accounts).

After disconnecting and reconnecting an account, retrieve the current ID before retrying. A saved workflow or brand mapping might contain a stale ID. Confirm the account belongs to the same authenticated Blotato user and platform as the request.

| Destination               | Identifier                              | Where to get it                                                                       |
| ------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- |
| Facebook Page             | `pageId`, required                      | MCP account `subaccounts`, or REST `GET /v2/users/me/accounts/:accountId/subaccounts` |
| LinkedIn company Page     | `pageId`, required for this destination | MCP account `subaccounts`, or the REST subaccounts endpoint                           |
| LinkedIn personal profile | Omit `pageId`                           | Use the LinkedIn `accountId`                                                          |
| Pinterest board           | `boardId`, required                     | `blotato_list_pinterest_boards`, or `GET /v2/social/pinterest/boards?accountId=...`   |
| YouTube playlist          | `playlistIds`, optional                 | MCP account `subaccounts`, or the REST subaccounts endpoint                           |

A returned Facebook Page is a destination to select, not permission to publish to the first Page in the list. LinkedIn Page IDs apply only when the user chooses a company Page. Omitting `pageId` selects the LinkedIn personal profile, not a company Page. Omit YouTube `playlistIds` unless playlist placement is requested.

MCP's `requiredFields` includes hints, not the customer's destination selection. Match the requested Page against `subaccounts`. An empty subaccount list does not prove the platform account has no Pages, since lookup failures also need investigation. Stop if a required destination ID is unavailable.

### Account not found

This error means the submitted ID did not match an account for the authenticated Blotato user and requested platform. It does not establish which value is wrong.

1. Read the failed request's `accountId`, `content.platform`, and `target.targetType`.
2. Retrieve accounts using the same credential or MCP connection used for publishing.
3. Select the returned account for the user's intended platform and destination.
4. Retrieve a separate Page ID when required. Do not put a Page ID in `accountId`.
5. Update the saved workflow mapping if it contains an old ID.

Different credentials, an ID from another platform, a Page ID used as an account ID, and an ID saved before reconnection are separate checks. An account dropdown reduces manual copying but does not guarantee a saved selection is current. An AI assistant must perform the lookup rather than assume it happened automatically.

Reconnect only when the connection state or returned error calls for it. If the same ID, platform, and authenticated user still produce the error after a fresh lookup, retain the request details and escalate. Do not assert a key mismatch without checking it. Never include keys or tokens in an escalation.

Before sending another publish request, verify the earlier submission's outcome through [Publishing status](#support-publishing-status).

### Keep post IDs separate

| ID                                         | Use                                                                                        |
| ------------------------------------------ | ------------------------------------------------------------------------------------------ |
| `postSubmissionId` from create-post        | Poll publishing status with `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId` |
| Published post ID from post listings       | Fetch analytics or comments for the published post                                         |
| Scheduled post `id` from schedule listings | Get, update, or cancel the scheduled post                                                  |
| Source `id`                                | Check source extraction                                                                    |
| Visual `item.id`                           | Check visual generation                                                                    |
| Comment `id`                               | Fetch the comment or check reply completion                                                |
| Conversation or message `id`               | Fetch the corresponding conversation or message                                            |
| Automation flow and trigger IDs            | Manage the automation or a trigger                                                         |

For `blotato_list_posts`, inspect each item's `state`. A scheduled item and a published item describe different records. Read the [List Posts response](#api-api-reference-list-posts) before passing an ID to another operation.

Updating an automation's content or triggers reissues its trigger IDs. Read the returned triggers before updating a trigger. See [DM automations](#api-api-reference-dm-automations).

Full lookup shapes: [Accounts](#api-api-reference-accounts), [Scheduled posts](#api-api-reference-schedules), [Comments](#api-api-reference-comments), and [Messages](#api-api-reference-messages).

***

## Async jobs and polling

Publishing, source extraction, visual generation, comment posting, and message sending finish after the initial request. Save the returned ID and check the operation's status before reporting success.

### Completion rules

| Operation      | Status lookup                                                      | Completion                                                                                                                     |
| -------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| Immediate post | `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`     | `published` succeeds. `failed` stops the workflow. `in-progress` needs another check.                                          |
| Scheduled post | Create response, then schedule lookup when needed                  | Report the returned `scheduledTime`. `scheduled` means scheduled, rather than published.                                       |
| Source         | `blotato_get_source_status` or `GET /v2/source-resolutions-v3/:id` | `completed` succeeds. `failed` stops the workflow. `queued` and `processing` need another check.                               |
| Visual         | `blotato_get_visual_status` or `GET /v2/videos/creations/:id`      | `item.status: done` supplies finished media. Stop on `item.error`, `creation-from-template-failed`, or `insufficient-credits`. |
| Comment        | `blotato_get_comment` or `GET /v2/comments/:commentId`             | `posted` succeeds. `failed` stops the workflow.                                                                                |
| Message        | `blotato_get_message` or `GET /v2/messages/:messageId`             | `sent` succeeds. `failed` stops the workflow.                                                                                  |

### MCP timing

`blotato_create_post` waits internally for up to 20 seconds for immediate publishing. `blotato_create_source` also waits internally for up to 20 seconds. If either returns unfinished work, use its status tool with the same ID.

1. Wait at least 10 seconds between post or source status calls.
2. Wait at least 15 seconds between visual status calls.
3. For queued comments and messages, space status checks to stay within the [endpoint rate limits](#api-guides-rate-limits).
4. When the agent's execution window ends, retain the ID and report processing status. Resume with a status lookup rather than another create request.

Inspect both MCP `isError` and the operation's status. A post or source status lookup returning `failed` is still a failed job even when the tool call itself succeeded. Source status failures use `message`. Post status failures use `errorMessage`.

Post `in-progress` is a fallback when no matching result record is found, not an independent queue-health check. Verify the saved submission ID and use a bounded polling loop. See [post lifecycle](#api-guides-post-lifecycle--look-up-existing-work).

### REST timing

REST create calls return a submission or creation response. The caller manages status checks. Use the intervals above as polling guidance and respect any rate-limit response. A `201` response confirms acceptance, rather than downstream publication.

### Visual states

Visual generation uses `queueing`, `generating-script`, `script-ready`, `generating-media`, `media-ready`, and `exporting` before `done`. Not every template traverses every state.

Inspect `item.error` on every response. A visual with an error has stopped even if its status remains `script-ready` or another intermediate state. `draft` is not a finished rendered visual. Follow [Create Visual](#api-api-reference-create-video) when choosing `render` and template inputs.

Use `item.mediaUrl` or `item.imageUrls` only after generation succeeds. Those URLs are ready for publishing and do not need a separate upload.

### Uploads are a separate flow

A presigned upload response provides 2 URLs. Upload the file bytes to `presignedUrl`, wait for the upload to succeed, and use `publicUrl`. There is no upload-status MCP tool. See [Media uploads and conversion](#api-guides-media-uploads-and-conversion).

For examples, see [Workflow recipes](#api-recipes-workflows). For failure recovery, see [Errors and retries](#api-guides-error-handling).

***

## Media uploads and conversion

Choose the input flow before publishing. Uploading a file and converting it to meet a platform's requirements are separate operations.

| Your input                                              | Next action                                                           |
| ------------------------------------------------------- | --------------------------------------------------------------------- |
| Public image or video URL                               | Pass it directly in `mediaUrls`. No upload step required.             |
| Finished Blotato visual                                 | Pass the returned `mediaUrl` or `imageUrls`. No upload step required. |
| File on the user's computer or in the agent's workspace | Complete a presigned upload, then pass `publicUrl`.                   |
| Private download link or editor/share page              | Obtain a public downloadable media URL, or upload the file bytes.     |

### Upload a local file

1. Call `blotato_create_presigned_upload_url` with `filename`, including its extension. For REST, call `POST /v2/media/uploads` with the same field.
2. Send an HTTP `PUT` to the returned `presignedUrl`, with the raw file bytes as the request body.
3. After the upload succeeds, pass the returned `publicUrl` in the publish request's `mediaUrls` array.

The PUT body is raw bytes. Do not send JSON or multipart form data. The assistant needs access to the file and an HTTP tool or shell to perform the PUT. Creating the URL alone does not upload the file.

If the assistant's environment blocks the upload destination, follow the [MCP network-egress troubleshooting](/start-with-an-ai-agent/mcp/faqs.md#which-blotato-domains-do-i-need-to-allowlist). For REST upload shapes, see [Upload Media](#api-api-reference-upload-media-v2-media).

### Check conversion requirements

Blotato validates media for the destination platform and performs supported conversions when required. Automatic video conversion is limited to videos no longer than 120 seconds. A longer video must already meet the applicable publishing requirements.

The processing queue checks the original file and existing variants. It reuses a valid file without conversion. If none is valid, it either requests a supported conversion of the original or returns the validation error. File upload does not imply every file is converted.

1. Check the [platform media requirements](/rest-api-reference/publish-post/media.md).
2. Check the file's container, codecs, dimensions, aspect ratio, duration, bitrate, and size.
3. If a video longer than 120 seconds needs cropping, resizing, or re-encoding, edit and export it before publishing.
4. Use a URL which serves the media without a login.
5. Submit the post and check its final publishing status.

An `.mp4` filename does not prove the container, codecs, or aspect ratio. Upload success does not establish platform compatibility.

For Instagram Reels, see the [Reel troubleshooting checklist](/social-accounts-and-platform-faqs/instagram/faqs.md#my-instagram-reel-is-under-300-mb-and-15-minutes-why-was-it-rejected). Account upload limits and platform file limits both apply. See [Billing and credits](/settings/billing-and-credits.md) and [Media requirements](/rest-api-reference/publish-post/media.md).

### Container identification errors

`Unsupported media format: mov,mp4,m4a,3gp,3g2,mj2` means Blotato did not recognize the file's container brand. The listed formats describe the probe's format family, not the file's exact type. This error is separate from the 120-second conversion limit.

1. Inspect the file's container metadata, including `major_brand`. Do not identify the format from its filename alone.
2. Export the source as MP4 with H.264 video and AAC audio. Renaming the extension does not change the container.
3. Inspect the exported file and check its dimensions, bitrate, size, and duration against the destination's requirements.
4. Verify the original submission's status before retrying. If the error persists, provide support with the submission ID and the exported file's metadata. Keep signed URLs and credentials out of support messages.

For the agent: the current parser recognizes MP4 brands `isom`, `iso1` through `iso6`, `mp41`, and `mp42`. It recognizes MOV through the exact padded brand `qt` . Missing or unrecognized brands produce the format-list error, even for a short video. A recognized container does not prove publishing compatibility.

***

## Post lifecycle

A post moves through submission, processing, and a published or failed outcome. Scheduling adds a wait until the chosen time.

| Result                                  | Meaning                                             | Agent response                                                 |
| --------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------- |
| Create response with `postSubmissionId` | Blotato accepted the request                        | Save the ID                                                    |
| `in-progress`                           | No failed, published, or scheduled result was found | Verify the saved submission ID and poll within a waiting limit |
| `scheduled`                             | The post is scheduled                               | Report `scheduledTime` and stop immediate polling              |
| `published`                             | Publishing completed                                | Return `publicUrl` when supplied                               |
| `failed`                                | Publishing stopped                                  | Explain `errorMessage` and resolve the cause                   |

### Publish now

1. Fetch the destination account and required IDs.
2. Submit the post once.
3. Follow [status polling](#api-guides-async-jobs-and-polling).
4. Report the returned outcome.

### Publish later

1. Resolve the user's requested date, time, and timezone.
2. Submit with a future `scheduledTime` or `useNextFreeSlot: true`.
3. Report the resolved UTC time in the user's timezone.
4. Use [scheduled-post management](#api-api-reference-schedules) for later edits or cancellation.

Do not keep polling a future scheduled post as if publication should finish now. Check publication after its scheduled time when the task requires it.

### Look up existing work

Use [List Posts](#api-api-reference-list-posts) to inspect published, scheduled, and failed records. Its `state.type` values describe list items, while the submission-status endpoint uses the `status` values above.

Keep [submission, scheduled, and published post IDs](#api-guides-accounts-and-identifiers) separate. A retry after an uncertain network result starts with looking up existing work. Repeating create-post risks a duplicate.

The submission-status endpoint does not verify a live queue job before returning `in-progress`. This fallback also applies when it finds no record for the supplied ID. Use the ID from the original create response and the original Blotato account credentials. Do not treat an arbitrary ID returning `in-progress` as evidence of accepted work. For missing or conflicting records, follow [Find a post's outcome before retrying](#support-publishing-status).

***

## Scheduling posts

Choose a specific publish time or the next free calendar slot, not both. An explicit `scheduledTime` does not bypass slot validation if `useNextFreeSlot` is also `true`. A scheduled result confirms the queue entry. Check publication after the chosen time if your workflow needs the live post URL.

### Choose a time

1. Resolve the user's date, time, and timezone. Ask for the timezone when it is unknown.
2. Convert the chosen instant to a future UTC ISO 8601 timestamp, such as `2030-06-15T15:00:00Z`. Replace example dates with the intended future date.
3. Set `scheduledTime` in the create-post request.
4. Read the returned `scheduledTime` and report it in the user's timezone.

For MCP, `scheduledTime` is a tool argument. For REST, it is a top-level sibling of `post`:

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "twitter",
      "text": "Scheduled post example",
      "mediaUrls": []
    },
    "target": { "targetType": "twitter" }
  },
  "scheduledTime": "2030-06-15T15:00:00Z"
}
```

The API parses and returns scheduled times in UTC. There is no separate timezone field in this create-post request. The web calendar's [timezone setting](/web-app-features/content-calendar/timezone.md) controls the calendar experience.

### Use the next free slot

1. Set up the calendar's [schedule slots](/web-app-features/content-calendar/create-schedule.md).
2. Set `useNextFreeSlot: true` in place of a specific `scheduledTime`.
3. Report the resolved time returned by Blotato.

If no matching slot or available time is found, post creation returns a validation error. Add or correct slots for the intended platform, account, and Page. Do not retry without scheduling fields unless the user authorizes immediate publishing.

In REST, `useNextFreeSlot` is a top-level sibling of `post`. For slot definitions and advanced REST scheduling, see [Weekly posting schedule](#api-api-reference-schedule-slots) and [Publish Post](#api-api-reference-publish-post).

### Edit or cancel a scheduled post

1. List schedules with `blotato_list_schedules` or `GET /v2/schedules`.
2. Read the selected scheduled post with `blotato_get_schedule` or `GET /v2/schedules/:id`.
3. Update it with `blotato_update_schedule` or `PATCH /v2/schedules/:id`, or cancel it with `blotato_delete_schedule` or `DELETE /v2/schedules/:id`.
4. Check the returned state or re-read the schedule to verify the change.

Use the scheduled post's ID from the schedule listing. See [Scheduled Posts](#api-api-reference-schedules) for update fields and preserved content.

***

## API rate limits

Blotato applies route-specific request limits. These limits are separate from subscription limits, credits, and social platform publishing limits.

| Operation                                                               | Requests per minute |
| ----------------------------------------------------------------------- | ------------------: |
| Create post                                                             |                  30 |
| List posts or get post submission status                                |   60 for each route |
| Get post analytics                                                      |                  60 |
| Upload media from a URL with `POST /v2/media`                           |                  30 |
| Create a presigned upload URL                                           |                 120 |
| List Pinterest boards                                                   |                  45 |
| List comments or get a comment                                          |   60 for each route |
| Post a comment                                                          |                  30 |
| List conversations, get a conversation, list messages, or get a message |   60 for each route |
| Send a message                                                          |                  30 |
| List or get DM automations, runs, logs, or analytics                    |   60 for each route |
| Create, update, delete an automation, or update a trigger               |   30 for each route |

This table covers the listed routes. Consult each endpoint's reference for other limits. MCP tools call the underlying REST routes, so status polling and tool sequences consume requests too.

### Handle a limit response

1. Stop requests to the limited route after `429`.
2. Wait for the retry interval when the response supplies one.
3. Reduce concurrency and request frequency before resuming.
4. Use [polling intervals](#api-guides-async-jobs-and-polling) rather than repeated status calls without a delay.

A platform publishing restriction is a different limit. For example, the Starter plan's TikTok restriction counts 3 distinct TikTok accounts posted to during a rolling 24-hour window. It is not an API request-per-minute limit or a count of connected accounts. See [TikTok limitations](/social-accounts-and-platform-faqs/tiktok/limitations.md).

For credit purchases, see the limits in [Buy Credits](#api-api-reference-credits--buy-credits).

***

## Errors and retries

Read the returned error before changing the request. For REST requests, record the HTTP status and response body. For MCP, inspect the tool result and its `isError` flag. A request accepted by Blotato still needs its workflow's completion check.

For customer support, use [Diagnose and answer](#support-agent-triage) to preserve the client, failing stage, previous attempts, and human corrections. Do not repeat a setup question already answered in the conversation.

For missing, delayed, or duplicate posts, follow [Find a post's outcome before retrying](#support-publishing-status). An HTTP status or an inaccessible platform URL alone does not establish the underlying cause.

| Error or symptom                           | Next step                                                                                                                              |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `401`, invalid API key, or invalid session | Follow [authentication checks](#api-guides-authentication)                                                                             |
| Subscription-related `403`                 | Check [Billing](https://my.blotato.com/settings/billing)                                                                               |
| `404`                                      | Re-fetch the record and verify the ID type and account                                                                                 |
| Validation error, often `422`              | Correct the named field using the endpoint schema                                                                                      |
| `429`                                      | Wait for the retry interval if provided, then reduce request frequency                                                                 |
| Network error or `5xx` on a read           | Retry with a delay and a bounded attempt count                                                                                         |
| Network error after a create request       | Check existing work before submitting again                                                                                            |
| Post status `failed`                       | Read `errorMessage` and the [platform requirements](/platform-publishing/platforms.md)                                                 |
| Visual response contains `item.error`      | Stop polling and explain the generation error                                                                                          |
| Empty analytics or inbox result            | Check the feature's [sync timing and supported platforms](/web-app-features/analytics.md) or [inbox setup](/web-app-features/inbox.md) |
| Media fetch or conversion failure          | Follow the [media checklist](#api-guides-media-uploads-and-conversion)                                                                 |

### Inspect a failed request

1. Open the [Logs](https://my.blotato.com/logs).
2. Select the request.
3. Read its payload, response, and error message.
4. Correct the cause before resubmitting.

For n8n requests, the dashboard's FIX MY AUTOMATION action helps repair the workflow. See the [n8n walkthrough](/integrations-and-automation-templates/n8n/faqs.md#first-step-click-fix-my-automation-in-the-api-dashboard-n8n-only). This action is specific to n8n.

### Retry without duplicating work

1. Save every submission or creation ID returned by a successful create request.
2. If a status call fails, retry the status call with the saved ID.
3. If creation timed out without an ID, inspect the dashboard and existing posts or schedules before creating another item.
4. For a failed post, correct the error before a new submission. An unchanged retry often fails for the same reason.
5. Set an attempt or elapsed-time limit in your automation. Report unfinished work and its ID when the limit is reached.

Blotato does not expose a documented client-supplied idempotency header for create-post requests. Internal duplicate detection is not a guarantee for arbitrary changed payloads, times, or retries. Retain the submission ID, inspect existing work, and do not invent an idempotency header.

See [Error reference](/support/errors.md), [API FAQs](/start-with-an-ai-agent/faqs.md), [MCP FAQs](/start-with-an-ai-agent/mcp/faqs.md), and [Get support](/support/get-support.md) for specific cases.

***

## Diagnose and answer a support question

Scope: support agents answering questions about the website, MCP, REST, n8n, or Make. Customer assistants use the same checks before retrying a failed operation.

### Response contract

1. Answer the question using the context already supplied.
2. Give the next supported action for the customer's surface.
3. State what proves success or what remains unresolved.
4. Link the relevant section. A tutorial supplements the answer, not replaces it.

Do not ask a basic setup question again when the customer already answered it. Preserve the exact error, attempted steps, and human support corrections. A historical incident or an empty result does not prove a current outage.

### Identify the stage

| Customer symptom                                         | Checks before the next action                                                                                                                                                                                         | Evidence or stop condition                                                                                                                                                    |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| “Connected”, but no tools                                | Identify the client. Separate connector creation, authorization, callback, and tool discovery. Use [client setup](#api-mcp-setup).                                                                                    | A successful account-list call verifies authentication. A redirect alone does not.                                                                                            |
| API key rejected                                         | Check the provider, endpoint, header, full key, and paid-access state using [authentication checks](#api-guides-authentication). Never request the secret in chat.                                                    | Stop repeated writes. Record the sanitized error and HTTP status.                                                                                                             |
| Account not found, wrong account, or reconnected account | Re-list accounts and match platform, name, and destination. Retrieve Page or board IDs where required. See [identifier rules](#api-guides-accounts-and-identifiers).                                                  | No unambiguous matching destination means no publishing action.                                                                                                               |
| “Upload failed”                                          | Identify whether the artifact is workflow JSON or media. For media, distinguish local bytes, signed upload, Blotato fetching a host, and platform validation.                                                         | Use [media diagnosis](#support-agent-triage--media-upload-and-conversion), not a generic reconnect instruction.                                                               |
| Accepted request, but nothing published                  | Retrieve the saved submission ID with [post status](#api-api-reference-get-post). Inspect status and error fields. For missing or conflicting records, use [publishing-status diagnosis](#support-publishing-status). | Scheduled is not published. Unknown outcome is not permission to create again. `in-progress` alone does not prove a live queue job.                                           |
| Generation failed despite credits                        | Inspect the visual's error and template inputs. Check complete-job cost, not only a per-scene price.                                                                                                                  | Use [visual failure diagnosis](/web-app-features/videos/faqs.md#why-did-my-video-generation-fail-i-have-enough-credits). Do not infer the cause from a terminal status alone. |
| Edit, mute, download, or post an existing video          | Open the existing visual. Distinguish track audio, caption narration, preview state, export, and post draft.                                                                                                          | Use [Edit, mute, and export](/web-app-features/videos/edit-and-export.md). Create Post is not publication. Do not regenerate to solve a download question.                    |
| Empty analytics or inbox                                 | Check the operation's platform support, permissions, post eligibility, and collection timing.                                                                                                                         | [Analytics](/web-app-features/analytics.md) and [Inbox](/web-app-features/inbox.md) have different collection rules. Missing data is not 0 engagement.                        |
| Cancellation, refund, discount, or paid-but-inactive     | Separate published policy from the customer's verified subscription and payment state.                                                                                                                                | Use [billing policy](/settings/billing-and-credits.md). Account changes need authorized execution and a confirmed result.                                                     |

### Media upload and conversion

Scope: existing image or video files, not n8n workflow JSON or generated-template records.

1. Identify where the file exists: the customer's device, the assistant's workspace, or a remote URL.
2. For local media, verify the assistant has file access and an HTTP upload tool. A [presigned URL](#api-agent-workflows-upload) alone does not upload bytes.
3. For a client sandbox error, inspect the blocked host and client policy. Do not apply client allowlist instructions to a remote host rejecting Blotato's servers.
4. For remote media, verify a direct file response without interactive login. Browser access alone does not prove server-to-server access.
5. Inspect the actual container, codecs, file size, duration, and dimensions. An `.mp4` extension is not a metadata probe.
6. Apply [conversion and platform requirements](#api-guides-media-uploads-and-conversion). A video longer than 120 seconds is not automatically prohibited, but must already satisfy the destination's requirements.
7. After correction, verify the publishing result. Upload success does not prove publication.

### Repeated failure or unclear cause

1. Carry forward completed checks and their results. Do not resend the same checklist without new evidence.
2. Record the client or integration, operation, destination platform, timestamp and timezone, request/submission/visual ID, exact error, and sanitized request shape.
3. Record expected versus observed behavior and attempted fixes.
4. Stop if the next action risks another charge, duplicate post, wrong destination, or unauthorized account change.
5. Use [Get support](/support/get-support.md) with the evidence. Do not include API keys, OAuth codes, signed upload URLs, or unrelated customer data.

For a capability question with a confirmed answer, answer it directly. For example, Facebook publishing targets a Page, not a personal profile. Do not send setup tutorials or ask the customer to reconnect for an unsupported destination.

This page does not change support-ticket closure rules or grant tools permission to modify accounts.

***

## Find a post's outcome before retrying

Use this page when a post disappears from Upcoming Posts, remains in progress, shows published without a visible post, or appears twice. These symptoms do not establish the cause.

### Using the Blotato website

1. Record the intended social account or Page, content, and submission or scheduled time with timezone.
2. Check the [Scheduled](https://my.blotato.com/posts?status=scheduled), [Published](https://my.blotato.com/posts?status=published), and [Failed](https://my.blotato.com/posts?status=failed) filters in Posts for the same destination and content. Check the platform and time-window filters if a record is missing.
3. Open any returned platform URL and check the intended account on the platform.
4. Read the exact error before changing media, reconnecting an account, or retrying.
5. If those records do not establish the result, contact [support](/support/get-support.md) with the evidence below. Do not submit a replacement while the original outcome is unknown.

### Using MCP, REST, n8n, or Make.com

1. Retrieve the `postSubmissionId` from the original create response. Do not substitute a schedule ID, published-post ID, or guessed UUID.
2. Call `blotato_get_post_status` or [Get Post Status](#api-api-reference-get-post) with that ID and the original Blotato account credentials.
3. Follow the [status-specific completion rules](#api-guides-post-lifecycle).
4. Inspect [existing posts](#api-api-reference-list-posts) and schedules when the submission result is missing or inconsistent.
5. Retain the ID when your polling limit is reached. Escalate the unresolved result instead of repeating create-post.

The status endpoint returns `in-progress` when no matching failed, published, or scheduled record is found. It does not independently confirm a live queue job. A wrong or unavailable submission ID is not proven valid by this status. Verify the original create response before further polling.

### Interpret the evidence

| Observation                                                  | Supported conclusion and next step                                                                                                                                                        |
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Schedule disappeared at its due time                         | The schedule is no longer in that list. Check publishing results before assuming failure or success.                                                                                      |
| Published record, but platform URL is absent or inaccessible | Blotato recorded publication. Check the destination and platform account. An inaccessible URL alone does not prove moderation, deletion, or suppression.                                  |
| Failed result                                                | Read the error and check the destination before retrying. Correct an identified validation or connection problem. Retry a temporary failure only after checking for an existing post.     |
| 2 copies on the platform                                     | Record both URLs and submission times. Do not infer a timeout or blame a particular client without request evidence. Ask which copy to keep before removing content through the platform. |
| Several accounts fail at once                                | Investigate a shared service or incident. This pattern alone is not proof of an outage or its owner.                                                                                      |

### Escalation evidence

Include the client, destination, exact error, timestamps with timezone, `postSubmissionId` when available, relevant request/reference IDs, and platform URLs. State which checks are already complete. Switch to Developer Platform and open [Logs](https://my.blotato.com/logs) for recorded activity. Filter by Posts, Videos, Media, Accounts, or DM, then Processing, Failed, or Success. Search or filter by platform and account. Absence from a filtered log list does not prove no publishing attempt occurred.

Do not share API keys, OAuth codes, or signed upload URLs. Do not promise incident compensation or report a refund, credit adjustment, deletion, or reconnection as completed without confirmation.

***

## Publish and verify a post

Scope: external assistants using MCP or REST, including n8n and Make HTTP requests. Publishing requires paid access, a connected social account, and the user's authorization for the content and destination. For website actions, use [Create posts](/web-app-getting-started/posts.md).

Prompt for your assistant:

> Publish this post to my selected Blotato account. First list my accounts and confirm the destination from my request. Check the platform requirements, submit the post, and return its final status and public URL.

### Using MCP

The following text-only X example illustrates the complete path. Replace example IDs and text with the customer's requested destination and approved content. Other platforms have different requirements.

1. Call `blotato_list_accounts` with `{"platform":"twitter"}`. The result is an account array, not REST's `items` wrapper:

```json
[
  {
    "id": "98432",
    "platform": "twitter",
    "fullname": "Example account",
    "username": "example",
    "subaccounts": [],
    "requiredFields": {}
  }
]
```

2. Match the requested account. An empty list confirms authentication but requires connecting an account in [Accounts](https://my.blotato.com/accounts). Multiple matching destinations require clarification before a write.
3. Call `blotato_create_post`:

```json
{
  "accountId": "98432",
  "platform": "twitter",
  "text": "Approved post text.",
  "mediaUrls": []
}
```

4. Read the tool result, including `isError`, `status`, and any error text. If processing continues, retain `postSubmissionId` and call `blotato_get_post_status` at least 10 seconds later:

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

5. Follow the result table below. The create tool waits internally for up to 20 seconds. It might return a completed result without another status request. Do not submit the same post again to check progress.

#### Adapt the destination

1. Call `blotato_list_accounts` to find `accountId` and any Page IDs.
2. Read the destination's [platform guide](/platform-publishing/platforms.md). For Pinterest, call `blotato_list_pinterest_boards` to get `boardId`.
3. Supply public media URLs, or complete the [local upload](#api-agent-workflows-upload).
4. Call `blotato_create_post` with the account, platform, content, and required platform fields.
5. If the returned status is `in-progress`, call `blotato_get_post_status` with the returned `postSubmissionId`, waiting at least 10 seconds between checks.
6. Return `published` and the supplied `publicUrl`, or explain `failed` and its `errorMessage`.

The create tool waits internally for up to 20 seconds. Another create call starts another submission. Continue unfinished work with the status tool.

### Using REST

1. Fetch `GET /v2/users/me/accounts` and any required subaccounts or Pinterest boards.
2. Send `POST /v2/posts` using the [Publish Post schema](#api-api-reference-publish-post).
3. Save `postSubmissionId` from the response.
4. Poll `GET /v2/posts/:postSubmissionId` according to the [completion rules](#api-guides-async-jobs-and-polling).

Set `post.content.platform` and `post.target.targetType` to the same platform. Each create request has one account and destination.

#### Complete REST example

1. Send `GET https://backend.blotato.com/v2/users/me/accounts?platform=twitter` with the `blotato-api-key` header. Select the matching `items[].id` as `accountId`.
2. Send this body to `POST https://backend.blotato.com/v2/posts` with the same auth header and `Content-Type: application/json`:

```json
{
  "post": {
    "accountId": "98432",
    "content": {
      "platform": "twitter",
      "text": "Approved post text.",
      "mediaUrls": []
    },
    "target": { "targetType": "twitter" }
  }
}
```

3. Save the accepted response's `postSubmissionId`:

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

4. Send `GET https://backend.blotato.com/v2/posts/a1b2c3d4-e5f6-7890-abcd-ef1234567890` with the auth header. Use the ID from step 3, not the example ID. Wait at least 10 seconds between status requests.

### Completion and recovery

The status endpoint returns the same state vocabulary used by the MCP status tool:

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "published",
  "publicUrl": "https://x.com/example/status/123456"
}
```

| Result                            | Agent action                                                                                                                |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `published`                       | Report success and the returned `publicUrl` when supplied. Do not construct a guessed URL.                                  |
| `failed`                          | Stop. Read `errorMessage`, correct the cause, and obtain authorization for any new submission outside the original request. |
| `in-progress`                     | Poll the saved ID with a waiting limit. Report the ID and unresolved state if the limit is reached.                         |
| `scheduled`                       | Report the returned `scheduledTime`. Do not describe it as published.                                                       |
| No response or an uncertain write | Inspect the [Logs](https://my.blotato.com/logs) and existing work before creating again.                                    |

For media, complete [upload and validation](#api-agent-workflows-upload) before creating the post. For Instagram, Facebook, TikTok, Pinterest, YouTube, or LinkedIn, use the [platform-specific request](/platform-publishing/platforms.md), including required media and destination fields.

### Cross-post

1. Select the accounts requested by the user.
2. Adapt the media and target fields for each platform.
3. Create a separate submission for each destination.
4. Track each submission ID and report each outcome separately.

A success on one platform does not establish success on another. For publishing later, use [Scheduling](#api-agent-workflows-schedule).

***

## Schedule and manage posts

Prompt for your assistant:

> Schedule this post for the date, time, and timezone I specify. Use my selected account, then show the resolved publish time. If I ask to change a scheduled post, find and update the existing schedule.

### Create a schedule

1. Call `blotato_list_accounts` and select the destination.
2. Resolve the requested date and timezone, or choose the next free slot.
3. Call `blotato_create_post` with `scheduledTime` or `useNextFreeSlot: true`.
4. Verify the resolved `scheduledTime` and report it in the user's timezone. If no time is returned, retrieve status using the saved `postSubmissionId` before promising a schedule.

For REST, use `POST /v2/posts` with scheduling fields beside `post`. Follow [Scheduling posts](#api-guides-scheduling) for the request layout and UTC rules.

### Request and verification example

Use an [authenticated connection](#api-guides-authentication). For “June 15, 2030 at 9 AM in America/Denver,” the resolved UTC time is `2030-06-15T15:00:00Z`. Replace this example with the user's intended future date, accounting for daylight saving time on that date. Replace `accountId` with the selected Twitter/X account from lookup.

Call `blotato_create_post` once:

```json
{
  "accountId": "98434",
  "platform": "twitter",
  "text": "Registration for our workshop opens today.",
  "mediaUrls": [],
  "scheduledTime": "2030-06-15T15:00:00Z"
}
```

For REST, send this body to `POST https://backend.blotato.com/v2/posts` with `blotato-api-key: YOUR_API_KEY` and `Content-Type: application/json`:

```json
{
  "post": {
    "accountId": "98434",
    "content": {"platform": "twitter", "text": "Registration for our workshop opens today.", "mediaUrls": []},
    "target": {"targetType": "twitter"}
  },
  "scheduledTime": "2030-06-15T15:00:00Z"
}
```

Save the returned `postSubmissionId`. A REST acceptance response has this shape:

```json
{
  "postSubmissionId": "123e4567-e89b-12d3-a456-426614174000",
  "scheduledTime": "2030-06-15T15:00:00Z"
}
```

If further verification is needed, call `blotato_get_post_status` with `{"postSubmissionId": "RETURNED_ID"}`, or request `GET /v2/posts/RETURNED_ID` with the authentication header. A scheduled result has this shape:

```json
{
  "postSubmissionId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "scheduled",
  "scheduledTime": "2030-06-15T15:00:00Z"
}
```

Report “Scheduled for June 15 at 9 AM America/Denver,” not “Published.” For an unfinished result, wait at least 10 seconds between status checks and stop after a client wait budget, such as 2 minutes, with the saved ID and unresolved state. An uncertain result is not permission to create another post.

### Change or cancel

1. Call `blotato_list_schedules` to locate the scheduled item.
2. Call `blotato_get_schedule` to read its content and time.
3. Call `blotato_update_schedule` with the intended change, or `blotato_delete_schedule` to cancel.
4. Verify and report the resulting schedule state.

For REST, use the corresponding `GET`, `PATCH`, or `DELETE /v2/schedules/:id` operation. See [Scheduled Posts](#api-api-reference-schedules).

Scheduled-post IDs differ from submission and published-post IDs. Obtain the schedule ID from the listing. A scheduled result is the stopping point for this task. Publishing verification belongs after the scheduled time.

### Stop conditions and batches

Do not assume the create response has `status: "scheduled"`. A returned submission ID or a successful HTTP response alone does not establish the resolved schedule. Use the [scheduling rules](#api-guides-scheduling) and [post status](#api-api-reference-get-post).

Before `useNextFreeSlot`, verify slots exist for the intended destination. If no matching slot or available slot time is found, post creation returns a validation error. Do not remove the scheduling field and retry, since a request without scheduling publishes immediately.

For a batch, record each destination, resolved time, submission ID, schedule ID when available, and outcome. Retry only a confirmed failed item after correcting its cause. Do not recreate successful items because another item failed. Scheduling a set of posts does not create a recurring content-generation automation.

***

## Upload local media

Scope: an existing image or video file. This is not the import flow for an n8n workflow template and does not create a generated Visual in the Videos library.

Prompt for your assistant:

> Upload this file to Blotato using a presigned upload URL. Send the raw bytes with HTTP PUT, confirm upload success, and use the returned public URL for my post.

1. Ensure the assistant has access to the file in its workspace.
2. Call `blotato_create_presigned_upload_url` with the filename and extension.
3. Upload the bytes to `presignedUrl` with an HTTP PUT.
4. Use `publicUrl` in `blotato_create_post` after the upload succeeds.
5. Follow [publishing verification](#api-agent-workflows-publish).

For REST, create the upload URLs with `POST /v2/media/uploads`. The upload itself goes to the returned signed URL, rather than to the REST API endpoint.

### Request and result

Call `blotato_create_presigned_upload_url` with this argument, or send it as JSON to `POST https://backend.blotato.com/v2/media/uploads` with the `blotato-api-key` header:

```json
{
  "filename": "video.mp4"
}
```

The response contains `presignedUrl` and `publicUrl`. Preserve the returned values. Do not derive one URL from the other or share the signed URL in a support reply.

1. Send `PUT` to `presignedUrl` with the raw file bytes. Do not use JSON or multipart form data.
2. Check the upload response for success before using `publicUrl`.
3. Confirm the file meets the [plan and platform limits](/rest-api-reference/publish-post/media.md).
4. Pass `publicUrl` in the publish request's `mediaUrls` array.
5. Verify the submission's final state separately.

If the client blocks the upload host, resolve the client permission or use a supported environment. If Blotato cannot fetch a remote source, inspect the source host's response instead. These are different request directions. See [stage-based diagnosis](#support-agent-triage--media-upload-and-conversion).

If the file already has a public download URL, pass it directly in `mediaUrls`. No upload step required. If the assistant lacks file or HTTP access, use a client with those capabilities or provide a public media URL.

See [Media uploads and conversion](#api-guides-media-uploads-and-conversion), [Upload Media reference](#api-api-reference-upload-media-v2-media), and [MCP upload troubleshooting](/start-with-an-ai-agent/mcp/faqs.md).

***

## Create and publish a visual

Scope: external API/MCP visual generation, including n8n and Make. The in-app AI Agent's video restriction is not a restriction on this workflow. Generation consumes credits according to the requested job. Confirm authorization before starting a new paid attempt.

Prompt for your assistant:

> Find a Blotato template for the image, carousel, or video I describe. Read its inputs, generate the visual, and check its status until it finishes or returns an error. Show me the finished media before following my publishing instructions.

### Using Blotato API or MCP

1. Call `blotato_list_visual_templates`, or `GET /v2/videos/templates`, to select a template and inspect its inputs.
2. Call `blotato_create_visual`, or `POST /v2/videos/from-templates`, with `templateId`, template-specific `inputs`, a prompt when needed, and `render: true` for rendered output.

Use the exact template ID returned by the listing. Do not strip a path ID to its UUID. MCP's template listing supplies `id`, `description`, and `inputs`. For REST, request `fields=id,name,description,inputs` when you need the template name and input schema.

3. Save the returned `item.id`.
4. Call `blotato_get_visual_status`, or `GET /v2/videos/creations/:id`, at least 15 seconds apart.
5. Stop when `item.status` is `done`, or when `item.error` or a failure state appears.
6. Use the returned `item.mediaUrl` or `item.imageUrls` for the [publish workflow](#api-agent-workflows-publish).

Do not invent template input fields. Use the template's current schema. [Inspect inputs without generating](#api-api-reference-create-video--inspect-inputs-without-generating-a-visual) instead of running a blank generation request. Generated media URLs do not need another upload. The completed visual has not been posted to a social platform until the publish workflow succeeds.

See [Create Visual](#api-api-reference-create-video), [Get Visual Status](#api-api-reference-find-video), and the [template catalog](#api-visuals-README).

### Request and output example

1. Retrieve templates and inspect the current inputs. The following quote-card template is an example, not a substitute for choosing the customer's requested output. Keep the complete template ID.
2. Call `blotato_create_visual` with this shape. For REST, send the same body to `POST https://backend.blotato.com/v2/videos/from-templates` with `Content-Type: application/json` and the `blotato-api-key` header:

```json
{
  "templateId": "/base/v2/quote-card/77f65d2b-48cc-4adb-bfbb-5bc86f8c01bd/v1",
  "inputs": {
    "title": "Daily practice",
    "quotes": ["Practice the skill you want to improve every day."],
    "aspectRatio": "4:5"
  },
  "render": true
}
```

3. Save `item.id` from the response. A `queueing` status means accepted, not finished:

```json
{
  "item": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "queueing"
  }
}
```

4. Call `blotato_get_visual_status` with `{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}`, or `GET /v2/videos/creations/a1b2c3d4-e5f6-7890-abcd-ef1234567890`. Substitute the saved ID and wait at least 15 seconds between checks.
5. On `done`, read the returned `item.imageUrls` for image outputs or `item.mediaUrl` for video. Do not guess the URL or reuse the source input as the generated result.
6. Stop on `item.error`, `creation-from-template-failed`, or `insufficient-credits`. A draft or `script-ready` record without completed media is not ready to publish. Inspect [visual status](#api-api-reference-find-video) for next steps.
7. Review the output before a separate [publish request](#api-agent-workflows-publish). Use the returned URLs directly without another upload.

Set a waiting limit. If generation remains unfinished, return its ID and current state. Do not create another paid job because a status request failed or time elapsed. For a brand requirement, follow [Brand Kit](/settings/brand-kit.md) because REST and MCP expose different fields.

### Using Blotato Web App

1. Follow [Make your first AI video](/web-app-features/videos/make-your-first-ai-video.md) to choose a template and generate a visual.
2. Review the finished output.
3. Follow the [recommended workflow](/web-app-features/videos/recommended-workflow.md) to publish it.

For images, see [AI Images](/web-app-features/ai-images.md). For credit questions, see [Video FAQs](/web-app-features/videos/faqs.md).

***

## Extract and repurpose content

Prompt for your assistant:

> Extract the content from this source with Blotato, then adapt it for the social platforms I choose. If a platform needs media, use media I supply or generate a visual before publishing.

### Before starting

Use an [authenticated connection](#api-guides-authentication). Choose `sourceType` explicitly. Use `text` for text or `perplexity-query`, and `url` for article, YouTube, Twitter/X, TikTok, audio, or PDF sources. A URL alone does not choose the source type.

### MCP example

1. Call `blotato_create_source` with the user's source. This example uses supplied text and requires no external source URL.

```json
{
  "sourceType": "text",
  "text": "Our workshop opens on October 15. Registration closes on October 10."
}
```

2. Save the returned `id`. The tool waits internally for up to 20 seconds. If the result is still `queued` or `processing`, call `blotato_get_source_status` with this shape, replacing the example ID.

```json
{"id": "123e4567-e89b-12d3-a456-426614174000"}
```

### REST equivalent

Send the following body to `POST https://backend.blotato.com/v2/source-resolutions-v3` with `blotato-api-key: YOUR_API_KEY` and `Content-Type: application/json`. REST nests the source fields. MCP does not.

```json
{
  "source": {
    "sourceType": "text",
    "text": "Our workshop opens on October 15. Registration closes on October 10."
  }
}
```

A `201` response contains the job ID, not extracted content:

```json
{"id": "123e4567-e89b-12d3-a456-426614174000"}
```

Read `GET /v2/source-resolutions-v3/123e4567-e89b-12d3-a456-426614174000` with the same authentication header. A completed response has this shape. The title and content below are illustrative, not guaranteed output text.

```json
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "status": "completed",
  "title": "Workshop dates",
  "content": "Our workshop opens on October 15. Registration closes on October 10."
}
```

### Completion and recovery

1. Wait at least 10 seconds between status requests for an unfinished job. Use a bounded wait, such as 2 minutes, then report the saved ID and pending state. This is a client wait budget, not a service completion promise.
2. On `completed`, use the returned `content` and `title`. Do not treat an accepted ID as extracted content. If MCP content ends with `[output truncated]`, it is not the full extraction. Use the REST lookup with the same ID when your client has an authorized API connection, or disclose the incomplete content. Repeated MCP lookups do not paginate the text.
3. On REST or MCP status lookup `failed`, inspect `message`. The MCP create tool reports extraction failure through `isError` with the source ID, while the status tool returns a `failed` status in its JSON result. Check both the tool error flag and the operation status. Correct the source or access problem before another extraction attempt.
4. After a timeout, look up the saved ID instead of submitting another extraction. If no ID was received, report the uncertain outcome before retrying.
5. Adapt the content to the user's request. Stop after returning the content for an extraction-only request. For publication, use existing media, or omit media for text-only posts supported by the selected platform. [Create a visual](#api-agent-workflows-visuals) only when new media is requested. Follow [Publish and verify](#api-agent-workflows-publish) with the user's publication approval.

Extraction produces source content. It does not itself download a reusable social video or publish a post. An Instagram caption without an image or video is insufficient for publishing.

See [Create Source](#api-api-reference-create-source) and [Get Source](#api-api-reference-get-source) for source-specific requirements and errors.

***

## Read post analytics

Prompt for your assistant:

> Compare my posts for the period I specify. Use Blotato's returned metrics and snapshot dates. Identify missing metrics and explain refresh timing before drawing conclusions.

### Select the posts

Use an [authenticated connection](#api-guides-authentication). Resolve the user's date range and timezone before requesting a ranking. `since` and `until` filter publication dates, not the dates on which engagement occurred. These results are not a daily engagement breakdown.

Call `blotato_list_top_posts` with a supported ranking metric. This example requests up to 10 Instagram posts published in the specified UTC interval:

```json
{
  "platform": "instagram",
  "since": "2026-09-01T00:00:00Z",
  "until": "2026-09-08T00:00:00Z",
  "sortBy": "views_count",
  "limit": 10
}
```

For REST, request `GET https://backend.blotato.com/v2/analytics` with these fields as URL-encoded query parameters and `blotato-api-key: YOUR_API_KEY`. Read `items` from the result. An empty list does not establish 0 engagement. Check date filters, platform coverage, and plan access.

### Inspect a post

Use `blotato_list_posts` to locate a specific post, or select a published post from the ranking. Call `blotato_get_post_analytics` with its published post ID:

```json
{"id": "123e4567-e89b-12d3-a456-426614174000"}
```

For REST, request `GET /v2/posts/123e4567-e89b-12d3-a456-426614174000/analytics` with the authentication header. This `id` is not `postSubmissionId`.

Read `metrics`, `lastFetchedAt`, `lastError`, and `history`. Each history entry has `fetchedAt` and `metrics`. Metric counts are returned as strings. Response metric names such as `viewsCount` differ from ranking keys such as `views_count`. Preserve numeric precision when comparing large counts.

### Completion and missing data

1. Report the returned metric values and snapshot times. Different collection times affect comparisons.
2. Treat missing or null metrics as unavailable, not 0. If the response is an error, preserve its message instead of manufacturing an empty result.
3. For `404`, distinguish a missing published post, unavailable plan access, and analytics not yet synced using the returned error. Do not retry post creation to obtain analytics.
4. Check the post's platform, eligibility, and publication time against [refresh checkpoints](/web-app-features/analytics.md). Reading analytics returns stored snapshots. It does not trigger collection.
5. Stop after reporting the available evidence and next checkpoint or unresolved error. Repeated immediate reads do not force a refresh. A new connection does not promise historical backfill. Follow [pending-data diagnosis](#api-analytics--posts-stuck-on-analytics-pending) before recommending reconnection.

Read the [API reference](#api-analytics) and [metrics reference](#api-analytics-metrics) before promising a metric or refresh interval. Platform, plan, and interface coverage differ.

***

## Read and reply to comments

Blotato's comments API and MCP tools support Instagram and Facebook.

Prompt for your assistant:

> Find comments on the posts I select, summarize them, and draft replies. When I ask you to send a reply, post it and verify its final status.

### Find the post and comment

1. Use an [authenticated connection](#api-guides-authentication) and an Instagram or Facebook account with comments enabled and the required permissions.
2. Call `blotato_list_posts` to find the selected published post's Blotato ID.
3. Call `blotato_list_comments` with the following shape. Replace the example `postId` with the returned ID.

```json
{"postId": "123e4567-e89b-12d3-a456-426614174000", "limit": 50}
```

4. Read `items`. If a `cursor` is returned and more results are needed, pass it with the same filters in the next call. A missing cursor marks the end.
5. Select the intended comment. A reply's parent must be a top-level comment with `status: "posted"` on the same post. Replies to replies are not supported.

### Send an approved reply

Call `blotato_post_comment` once with the user's approved text and IDs from the lookup:

```json
{
  "postId": "123e4567-e89b-12d3-a456-426614174000",
  "parentCommentId": "123e4567-e89b-12d3-a456-426614174001",
  "text": "Thanks for your question. Registration closes on October 10."
}
```

Omit `parentCommentId` only when the user requested a new top-level comment. Do not omit it to bypass a reply error.

Save the returned comment `id`. Call `blotato_get_comment` with:

```json
{"commentId": "123e4567-e89b-12d3-a456-426614174002"}
```

For REST, use `GET https://backend.blotato.com/v2/comments?postId=PUBLISHED_POST_ID&limit=50`, `POST /v2/comments` with the same reply JSON, and `GET /v2/comments/RETURNED_COMMENT_ID`. Include `blotato-api-key: YOUR_API_KEY` on every request and `Content-Type: application/json` on POST. URL-encode query values.

### Completion and recovery

| Returned status          | Next action                                                                      |
| ------------------------ | -------------------------------------------------------------------------------- |
| `posted`                 | Report the reply as posted.                                                      |
| `queued` or `processing` | Read the same comment again. Do not send another reply.                          |
| `failed`                 | Stop and inspect `errorCode` and `errorMessage`.                                 |
| `deleted`                | Report the deleted state. Do not replace the comment without the user's request. |

Use a client polling budget, such as 10-second intervals for up to 2 minutes. This is not a delivery-time guarantee. If still pending, retain the ID and report the pending state. After a send timeout with no ID, inspect recent comments before considering another write. If the outcome remains uncertain, stop rather than risk a duplicate reply.

Read [Comments](#api-api-reference-comments) for plan access, retention, and platform restrictions.

An empty inbox after connecting is a sync question. Follow [Inbox setup and timing](/web-app-features/inbox.md) before concluding the account has no comments.

***

## Read and reply to messages

Blotato's messaging API and MCP tools support Instagram and Facebook. Replies are subject to the platform's messaging rules and windows.

Prompt for your assistant:

> Find the conversation I specify, read its recent messages, and draft a reply. When I ask you to send it, use the existing recipient identifiers and verify the send status.

### Find the recipient

1. Use an [authenticated connection](#api-guides-authentication) and confirm messaging permissions and the [platform messaging window](#api-api-reference-messages--messaging-windows).
2. Call `blotato_list_conversations`, filtering by the intended account and platform. Select the conversation from `items`. Follow returned cursors until the requested conversation is found or the results end.

```json
{"accountId": "98434", "platform": "instagram", "limit": 50}
```

3. Call `blotato_list_messages` with the selected conversation's `id`. Replace all example IDs with returned values.

```json
{"conversationId": "cnv_abc123", "limit": 50}
```

4. Read `direction` before selecting the reply recipient. For an `incoming` message, the other participant is `senderId`. For an `outgoing` message, the other participant is `recipientId`. Use the conversation's `accountId` as the sending Blotato account. Do not invent a recipient ID from a username or send to the connected account itself.

### Send an approved reply

For this Instagram example, assume the selected incoming message's `senderId` is `1784000000`. After the user approves the reply, call `blotato_send_message` once:

```json
{
  "accountId": "98434",
  "platform": "instagram",
  "recipientId": "1784000000",
  "text": "Thanks for asking. Registration closes on October 10."
}
```

For REST, send the following JSON to `POST https://backend.blotato.com/v2/messages` with `blotato-api-key: YOUR_API_KEY` and `Content-Type: application/json`. REST uses `target`, not MCP's top-level `platform`.

```json
{
  "accountId": "98434",
  "recipientId": "1784000000",
  "text": "Thanks for asking. Registration closes on October 10.",
  "target": {"targetType": "instagram"}
}
```

For Facebook, select the Page from account lookup and supply MCP `pageId` or REST `target.pageId` with the Facebook platform value. Do not reuse the Instagram target or select the first Page without matching the user's request.

REST lookups use `GET /v2/conversations?accountId=ACCOUNT_ID&platform=instagram&limit=50` and `GET /v2/messages?conversationId=CONVERSATION_ID&limit=50`. Use the authentication header and URL-encode query values.

### Verify the send

Save the returned message `id`. The initial `queued` state is not confirmation of delivery. Call `blotato_get_message` with:

```json
{"messageId": "msg_abc123"}
```

For REST, read `GET /v2/messages/RETURNED_MESSAGE_ID` with the authentication header.

1. On outgoing `sent`, report the message as sent. This does not establish the recipient read it.
2. On `failed`, stop and preserve `errorCode` and `errorMessage`. Resolve permissions, messaging-window, recipient, or account errors before another attempt.
3. For `queued` or `processing`, read the same ID within a bounded client wait, such as 10-second intervals for up to 2 minutes. If still pending, report the saved ID and state. This budget is not a delivery promise.
4. After a send timeout, inspect the saved message or recent conversation messages. Do not resend while the first attempt's outcome remains uncertain.

For buttons, quick replies, and comment-to-DM behavior, read the [Messages reference](#api-api-reference-messages) before choosing a message shape. For connection and history questions, see [Inbox setup](/web-app-features/inbox.md).

***

## Manage DM automations

Use DM automations for supported Instagram and Facebook comment or message triggers. Read the [DM Automations guide](/web-app-features/dm-automations.md) for trigger behavior, limits, gates, and platform restrictions.

Prompt for your assistant:

> Set up a Blotato DM automation for the account, trigger, and reply I describe. Review the trigger and reply with me, then apply my requested activation state. Show how I will check its runs and errors.

1. Call `blotato_list_accounts` to identify the account.
2. Call `blotato_list_automations` to inspect existing flows before creating a duplicate.
3. Read the [automation schema](#api-api-reference-dm-automations) and match the trigger and message to the user's request.
4. Call `blotato_create_automation` or `blotato_update_automation` for the requested flow.
5. Inspect the returned flow and activation state.
6. Use `blotato_list_automation_runs`, `blotato_list_automation_logs`, and `blotato_get_automation_analytics` to inspect results.

### Create an inactive Instagram example

Use an [authenticated connection](#api-guides-authentication) and the account ID from lookup. This example responds to incoming messages matching the keyword `workshop`. It does not schedule social posts or generate content on a timer.

Call `blotato_create_automation` after checking existing automations and confirming the trigger and reply with the user:

```json
{
  "accountId": "98434",
  "platform": "instagram",
  "name": "Workshop message reply",
  "triggers": [{"type": "message-received", "keywords": ["workshop"], "isActive": true}],
  "dmMessage": "Registration closes on October 10.",
  "buttons": [],
  "isActive": false
}
```

The trigger is enabled within the draft, but the flow remains inactive because `isActive` at the top level is `false`. Read `automation.id`, `automation.isActive`, and `automation.triggers` from the MCP result. Creating a draft is not activation.

For REST, send this JSON to `POST https://backend.blotato.com/v2/dm-automations` with `blotato-api-key: YOUR_API_KEY` and `Content-Type: application/json`:

```json
{
  "accountId": "98434",
  "platform": "instagram",
  "target": {"targetType": "instagram"},
  "name": "Workshop message reply",
  "triggers": [{"type": "message-received", "keywords": ["workshop"], "isActive": true}],
  "dmMessage": "Registration closes on October 10.",
  "buttons": [],
  "isActive": false
}
```

The REST result wraps the saved record in `flow`, not MCP's `automation`. Save `flow.id`. Read it later with `GET /v2/dm-automations/RETURNED_FLOW_ID` and the authentication header. For Facebook, set the Facebook platform and target and include the selected Page ID. See the [automation schema](#api-api-reference-dm-automations).

### Activate and verify

If the user requests activation, call `blotato_update_automation` with the saved flow ID:

```json
{"automationId": "flow_abc123", "isActive": true}
```

For REST, send this body to `PATCH /v2/dm-automations/RETURNED_FLOW_ID`. REST requires the `patch` wrapper, unlike the MCP tool arguments:

```json
{"patch": {"isActive": true}}
```

Verify the returned activation state. Changes to an already active flow take effect immediately. Do not treat a content update to a live flow as an unpublished draft.

For an authorized test, send the matching message from a different account. Messages sent by the connected account do not start the run. Read the resulting runs with:

```json
{"automationId": "flow_abc123", "limit": 20}
```

For REST, use `GET /v2/dm-automations/RETURNED_FLOW_ID/runs?limit=20`. Inspect a selected run's logs with `blotato_list_automation_logs` or `GET /v2/dm-automations/RETURNED_FLOW_ID/logs?flowRunId=RUN_ID`. A completed test run verifies the tested trigger, not every future event. If no run appears, check account ownership, activation, and trigger matching before creating another flow. Preserve the flow ID and returned error if a write outcome is uncertain.

### Later changes

For one trigger's on/off state, use `blotato_update_automation_trigger` with current flow and trigger IDs. At least one trigger must stay active while the flow is live. Updating flow content or triggers reissues trigger IDs, so read the response before a later trigger update.

For REST, use the [DM Automations API](#api-api-reference-dm-automations). Trigger updates, flow updates, and archive operations have distinct schemas. Sending a test DM or creating a flow does not prove a real trigger run succeeded. Verify runs and logs.

***

## Check or add credits

Prompt for your assistant:

> Check my Blotato credit balance and account email. If I need more credits, tell me where to add them.

1. Call `blotato_get_credits` to read the balance, account email, pricing, and purchase limits.
2. State the returned account email before directing the user to purchase credits. Credits belong to the account and are non-transferable.
3. Direct the user to [Settings > Billing](https://my.blotato.com/settings/billing).
4. Recheck the balance after payment when requested.

### Request and result

Call `blotato_get_credits` without arguments, or request `GET https://backend.blotato.com/v2/credits` with `blotato-api-key: YOUR_API_KEY`. Read `creditsRemaining`, `accountEmail`, `purchaseQuantityRange`, and `pricePer1000CreditsUsd` from the response. Do not substitute another plan's monthly allocation for the current balance.

The MCP server does not list a credit-purchase tool. Do not invent a checkout link. After the user finishes payment in Billing, read the balance again when requested and preserve any payment error for [billing support](/settings/billing-and-credits.md).

See [Credits reference](#api-api-reference-credits) for balance, pricing, and REST checkout fields.

***

## REST API quickstart

Use this path for HTTP requests, n8n, or Make. To give instructions to Claude, Codex, or another assistant, start with [MCP setup](#api-mcp-setup).

Your first verified result is an account-list response. Publishing comes after account selection and platform checks.

### Get Started with Blotato API

The Blotato API allows you to:

* publish and schedule posts directly to social media platforms
* supports text, image, videos, reels, slideshows, carousels, threads, and stories
* create images, videos, slideshows, and carousels programmatically via templates

It is limited to paying subscribers in order to reduce spam and service abuse, keeping Blotato's integration in good standing with the social platforms.

***

### Plans That Include API

| Plan       | API Access |
| ---------- | ---------- |
| Free Trial | No         |
| Starter    | Yes        |
| Creator    | Yes        |
| Agency     | Yes        |

API access is included on every paid plan. Generating an API key from Settings > API immediately ends a free trial and activates your paid Starter subscription.

***

### Base URLs

Blotato has two base URLs. Use the one that matches your integration:

| Integration                                                                           | Base URL                         |
| ------------------------------------------------------------------------------------- | -------------------------------- |
| REST API (direct HTTP, n8n, Make)                                                     | `https://backend.blotato.com/v2` |
| MCP Server (ChatGPT, Claude Code, Claude Cowork, Claude Desktop, Cursor, Antigravity) | `https://mcp.blotato.com/mcp`    |

`api.blotato.com` is **not a valid base URL**. If your AI tool reports a DNS error for `api.blotato.com`, it guessed wrong. Use one of the two URLs above.

***

### 1. Get Your API Key

:exclamation:**IMPORTANT: this will end your free trial immediately and start your paid subscription.**

Go to [Accounts](https://my.blotato.com/accounts) > API > click "Generate API Key".

***

### 2. Connect Social Accounts

Go to [Accounts](https://my.blotato.com/accounts) and connect your social accounts. If you get stuck, more information here:

[Related guide](https://help.blotato.com/settings/social-accounts)

***

### 3. Verify access

Ask your agent or HTTP client to send this request with your API key:

```http
GET /v2/users/me/accounts HTTP/1.1
Host: backend.blotato.com
blotato-api-key: YOUR_API_KEY
```

A successful response contains `items`. An empty `items` array confirms access but means no social accounts are connected. Use each account's returned `id` as `accountId`.

Next, follow [Publish and verify a post](#api-agent-workflows-publish), or choose another [agent workflow](/agent-workflows/agent-workflows.md). Use [Accounts and identifiers](#api-guides-accounts-and-identifiers) for Page, board, and playlist IDs.

### Optional: install the Blotato integration

#### n8n

1. Follow the [Blotato node installation guide](/integrations-and-automation-templates/n8n/n8n-blotato-node.md#install-the-blotato-node) for n8n Cloud or self-hosted n8n.
2. [Configure and verify the credential](/integrations-and-automation-templates/n8n/n8n-blotato-node.md#configure-and-verify-the-credential).
3. Use the [first-post workflow](/integrations-and-automation-templates/n8n/n8n-basics.md) after selecting the intended account.

#### Make

1. Open any scenario in Make
2. Click the "+" icon to add a module
3. Search for "Blotato"
4. Select the Blotato module

***

### Choose an automation tutorial

**New to building automations?** Start here:

* [Build Your First AI Automation](/integrations-and-automation-templates/templates/11-build-your-first-ai-automation.md) - Build an n8n source-to-visual-to-post workflow with completion checks.

Choose your preferred integration path:

* [MCP Server](/start-with-an-ai-agent/mcp.md) - control Blotato from ChatGPT, Claude.ai, Claude Desktop, Claude Code, Cursor, and more with natural language
* [n8n - post everywhere](/integrations-and-automation-templates/templates/1-post-everywhere.md)
* [Make - post everywhere](/integrations-and-automation-templates/templates/1-post-everywhere.md)
* [REST API - OpenAPI reference](/rest-api-reference/openapi-reference.md) and [Examples Below](#api-start--raw-rest-api-calls---examples)

Blotato has official Make.com and n8n nodes. Zapier does not have an official Blotato app. In Zapier, use Webhooks by Zapier to send authenticated HTTP requests to the [Blotato API](#api-api-reference-publish-post).

Check out more workflow automation templates here:

[Related guide](https://help.blotato.com/api/templates)

***

### Troubleshoot errors

Use the Logs and click on each request to see full payload, response, and error message:

**Logs (for debugging):** <https://my.blotato.com/logs>

**FIX MY AUTOMATION (n8n only):** On a failed n8n request in the Logs, click the green **FIX MY AUTOMATION** button and Blotato AI will attempt to fix your n8n workflow automatically. This feature is for n8n only -- it does not work for Make, Claude, MCP, or direct REST API calls. Full walkthrough: [Fix My Automation](/integrations-and-automation-templates/n8n/faqs.md#first-step-click-fix-my-automation-in-the-api-dashboard-n8n-only).

***

### Raw REST API Calls - Examples

#### Authentication

To authenticate API requests, include your Blotato API key in the request headers.

**Authentication Header**

```
blotato-api-key: YOUR_API_KEY
```

Requests without a valid API key will be rejected and 401 error will be returned.

Your API key sometimes ends with one or more `=` characters. Copy the full value, including these characters. Use the quoting required by your client or configuration format. For a `401`, follow [authentication checks](#api-guides-authentication).

#### Step 0: Get Your Account IDs

Before publishing, fetch your connected accounts to get the `accountId`:

```
GET https://backend.blotato.com/v2/users/me/accounts HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

Use the `id` from the response as your `accountId`. For Facebook and LinkedIn, also fetch subaccounts to get `pageId`. See [Accounts reference](#api-api-reference-accounts) for details.

#### Post to a Platform Immediately

```
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Hello, world!",
      "mediaUrls": [],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  }
}
```

After submission, save `postSubmissionId` and poll `GET /v2/posts/:postSubmissionId` until `published` or `failed`. A `201` create response confirms acceptance. Follow the [completion rules](#api-guides-async-jobs-and-polling) before reporting success.

#### Post at a Scheduled Time

```
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Scheduled post example",
      "mediaUrls": [],
      "platform": "facebook"
    },
    "target": {
      "targetType": "facebook",
      "pageId": "987654321"
    }
  },
  "scheduledTime": "2030-06-15T15:00:00Z"
}
```

Replace the example date with the user's intended future time in UTC. To use the next available calendar slot, replace `scheduledTime` with `useNextFreeSlot: true`. Both are top-level fields, outside `post`. See [Scheduling](#api-guides-scheduling).

#### Post a Twitter Thread with Multiple Posts

```
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "This is the first tweet in the thread.",
      "mediaUrls": [],
      "platform": "twitter",
      "additionalPosts": [
        {
          "text": "Here's the second tweet, adding more info.",
          "mediaUrls": []
        },
        {
          "text": "And here's the third tweet to conclude!",
          "mediaUrls": []
        }
      ]
    },
    "target": {
      "targetType": "twitter"
    }
  }
}
```

#### Attach Media to Post (images and videos)

Pass any publicly accessible image/video URL into the `mediaUrls` parameter. No upload step required. Blotato handles the media transfer.

For local files without a public URL, use the [Presigned Upload](#api-api-reference-upload-media-v2-media--presigned-upload-local-files) endpoint to upload directly to Blotato. No Google Drive or S3 needed.

```
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Check out this image!",
      "mediaUrls": [
        "https://example.com/image.jpg"
      ],
      "platform": "instagram"
    },
    "target": {
      "targetType": "instagram"
    }
  }
}
```

The optional Upload Media endpoint is still available if you need to host media on Blotato's servers. See [Upload Media](#api-api-reference-upload-media-v2-media).

***

### For AI Agents

If you are an AI agent or LLM integration, start with the plain-text API reference:

[API Reference for LLMs](/start-with-an-ai-agent/llm.md)

This index links to references by task, including request examples, status values, and workflow instructions.

For async workflow patterns and code examples, see [Protocol and Recipes](#api-recipes-workflows).

For the full endpoint reference, see [API Reference](/rest-api-reference/api-reference.md).

***

## Public API operation index

This index covers Blotato's public operations in the [OpenAPI document](https://backend.blotato.com/openapi.json). Use the linked reference for parameters and responses.

Base URL: `https://backend.blotato.com`. The paths below include `/v2`. Authenticate with `blotato-api-key`.

| Method | Path                                               | Reference                                                                                   |
| ------ | -------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| GET    | `/v2/dm-automations`                               | [List automations](#api-api-reference-dm-automations--list-dm-automations)                  |
| POST   | `/v2/dm-automations`                               | [Create automation](#api-api-reference-dm-automations--create-dm-automation)                |
| GET    | `/v2/dm-automations/{id}`                          | [Get automation](#api-api-reference-dm-automations--get-dm-automation)                      |
| PATCH  | `/v2/dm-automations/{id}`                          | [Update automation](#api-api-reference-dm-automations--update-dm-automation)                |
| DELETE | `/v2/dm-automations/{id}`                          | [Archive automation](#api-api-reference-dm-automations--delete-dm-automation)               |
| GET    | `/v2/dm-automations/{id}/runs`                     | [List automation runs](#api-api-reference-dm-automations--list-runs)                        |
| GET    | `/v2/dm-automations/{id}/logs`                     | [List automation logs](#api-api-reference-dm-automations--list-logs)                        |
| GET    | `/v2/dm-automations/{id}/analytics`                | [Get automation analytics](#api-api-reference-dm-automations--get-analytics)                |
| PATCH  | `/v2/dm-automations/{flowId}/triggers/{triggerId}` | [Update trigger](#api-api-reference-dm-automations--update-dm-automation-trigger)           |
| GET    | `/v2/comments`                                     | [List comments](#api-api-reference-comments--list-comments)                                 |
| POST   | `/v2/comments`                                     | [Post comment](#api-api-reference-comments--post-comment)                                   |
| GET    | `/v2/comments/{commentId}`                         | [Get comment](#api-api-reference-comments--get-comment)                                     |
| GET    | `/v2/credits`                                      | [Get credits](#api-api-reference-credits--get-credit-balance)                               |
| POST   | `/v2/credits`                                      | [Buy credits](#api-api-reference-credits--buy-credits)                                      |
| POST   | `/v2/media`                                        | [Upload URL](#api-api-reference-upload-media-v2-media)                                      |
| POST   | `/v2/media/uploads`                                | [Upload local file](#api-api-reference-upload-media-v2-media--presigned-upload-local-files) |
| GET    | `/v2/conversations`                                | [List conversations](#api-api-reference-messages--list-conversations)                       |
| GET    | `/v2/conversations/{conversationId}`               | [Get conversation](#api-api-reference-messages--get-conversation)                           |
| GET    | `/v2/messages`                                     | [List messages](#api-api-reference-messages--list-messages)                                 |
| POST   | `/v2/messages`                                     | [Send message](#api-api-reference-messages--send-message)                                   |
| GET    | `/v2/messages/{messageId}`                         | [Get message](#api-api-reference-messages--get-message)                                     |
| GET    | `/v2/integrations/oauth2/linkedin/pages`           | [Find LinkedIn pages](#api-api-reference-accounts--list-available-linkedin-pages)           |
| GET    | `/v2/social/pinterest/boards`                      | [List Pinterest boards](#api-api-reference-accounts--list-pinterest-boards)                 |
| GET    | `/v2/posts`                                        | [List posts](#api-api-reference-list-posts)                                                 |
| POST   | `/v2/posts`                                        | [Create post](#api-api-reference-publish-post)                                              |
| GET    | `/v2/posts/{postSubmissionId}`                     | [Get submission status](#api-api-reference-get-post)                                        |
| GET    | `/v2/posts/{id}/analytics`                         | [Get post analytics](#api-analytics--get-post-analytics)                                    |
| GET    | `/v2/analytics`                                    | [Rank posts](#api-analytics--list-top-performing-posts)                                     |
| GET    | `/v2/analytics/platforms`                          | [Compare platforms](#api-analytics--list-performance-by-platform)                           |
| GET    | `/v2/analytics/summary`                            | [Get analytics summary](#api-analytics--get-analytics-summary)                              |
| GET    | `/v2/published-posts`                              | [Search published posts](#api-api-reference-list-published-posts)                           |
| GET    | `/v2/published-posts/{id}`                         | [Get a published post](#api-api-reference-list-published-posts--get-a-published-post)       |
| GET    | `/v2/schedules`                                    | [List scheduled posts](#api-api-reference-schedules--list-scheduled-posts)                  |
| GET    | `/v2/schedules/{id}`                               | [Get scheduled post](#api-api-reference-schedules--get-scheduled-post)                      |
| PATCH  | `/v2/schedules/{id}`                               | [Update scheduled post](#api-api-reference-schedules--update-scheduled-post)                |
| DELETE | `/v2/schedules/{id}`                               | [Delete scheduled post](#api-api-reference-schedules--delete-scheduled-post)                |
| POST   | `/v2/source-resolutions-v3`                        | [Extract source](#api-api-reference-create-source)                                          |
| GET    | `/v2/source-resolutions-v3/{id}`                   | [Get source status](#api-api-reference-get-source)                                          |
| GET    | `/v2/users/me`                                     | [Get user](#api-api-reference-users)                                                        |
| GET    | `/v2/users/me/accounts`                            | [List accounts](#api-api-reference-accounts)                                                |
| GET    | `/v2/users/me/accounts/{accountId}/subaccounts`    | [List subaccounts](#api-api-reference-accounts--list-subaccounts-pages)                     |
| DELETE | `/v2/videos/{id}`                                  | [Delete visual](#api-api-reference-delete-video)                                            |
| GET    | `/v2/videos/creations/{id}`                        | [Get visual status](#api-api-reference-find-video)                                          |
| POST   | `/v2/videos/from-templates`                        | [Create visual](#api-api-reference-create-video)                                            |
| GET    | `/v2/videos/templates`                             | [List templates](#api-visuals-README)                                                       |

### Additional documented routes

[Account usage](#api-api-reference-users--get-usage) and [schedule-slot management](#api-api-reference-schedule-slots) are implemented and documented separately, but are absent from this OpenAPI export. Their absence here does not mean the features are unavailable.

For first-time setup, use the [REST quickstart](#api-start). For tool calls, use the [MCP tool catalog](#api-mcp-tools). The MCP catalog is not a generated copy of every REST operation.

***

## Users

### General

Use these endpoints to verify your API key and check account usage.

### Endpoints

<mark style="color:blue;">**GET**</mark> `/v2/users/me`

Fetches the current user's information, including subscription status and plan. This endpoint is useful for verifying that your API key is valid and referencing your own `userId`.

#### Response Keys

| Name                 | Type     | Description                                                     |
| -------------------- | -------- | --------------------------------------------------------------- |
| `id`                 | `string` | The unique ID of the user.                                      |
| `subscriptionStatus` | `string` | The user's subscription status (e.g., `active`, `generic_pro`). |
| `subscriptionPlan`   | `string` | The user's plan tier: `starter`, `creator`, or `agency`.        |
| `apiKey`             | `string` | **\[Sensitive]** The user's API key.                            |

***

## Examples

### Get verification info

```http
GET https://backend.blotato.com/v2/users/me HTTP/1.1
blotato-api-key: blt_...
```

**Response 200 OK**

```json
{
  "id": "e931cdad-0c31-4191-8930-745a76c8e31a",
  "subscriptionStatus": "active",
  "subscriptionPlan": "creator",
  "apiKey": "blt_..."
}
```

***

<mark style="color:blue;">**GET**</mark> `/v2/users/me/usage`

Returns the current user's connected-account usage, active-contact usage, and active-contact limit.

Use this endpoint before connecting more accounts or sending messages at scale so you know how close you are to your plan limits. For the limit values on each plan, see [Plan Limits](/settings/billing-and-credits.md#plan-limits).

#### Response Keys

| Name            | Type                       | Description                                                                                                                                                                                    |
| --------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `accounts`      | `integer`                  | Number of connected channels counting toward the connected-accounts limit. Facebook Pages count. The Facebook login itself does not count. LinkedIn profiles and LinkedIn company Pages count. |
| `contacts`      | `integer`                  | Number of contacts messaged this month, counting toward the active-contacts limit.                                                                                                             |
| `contactsLimit` | `integer` or `"unlimited"` | Maximum active contacts you can message this month. This matches your current account limit, including any custom active-contact limit on your account.                                        |

### Get usage

```http
GET https://backend.blotato.com/v2/users/me/usage HTTP/1.1
blotato-api-key: blt_...
```

**Response 200 OK**

```json
{
  "accounts": 12,
  "contacts": 438,
  "contactsLimit": 6000
}
```

***

## Accounts

### List Connected Accounts

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/users/me/accounts`

**Method:** `GET`

#### Description

Returns all social media accounts connected to your Blotato account. Use this to get the `accountId` required for [publishing posts](#api-api-reference-publish-post).

#### Query Parameters

| Field      | Type     | Required | Description                                                                                                                        |
| ---------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `platform` | `string` | No       | Filter by platform. Values: `twitter`, `instagram`, `linkedin`, `facebook`, `tiktok`, `pinterest`, `threads`, `bluesky`, `youtube` |

The `platform` values here are the same values used in `content.platform` and `target.targetType` when publishing.

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "98432",
      "platform": "twitter",
      "fullname": "Jane Smith",
      "username": "janesmith"
    },
    {
      "id": "98433",
      "platform": "facebook",
      "fullname": "Jane Smith",
      "username": "janesmith"
    }
  ]
}
```

| Field              | Type     | Description                                              |
| ------------------ | -------- | -------------------------------------------------------- |
| `items`            | `array`  | List of connected accounts                               |
| `items[].id`       | `string` | Account ID. Use this as `accountId` when publishing.     |
| `items[].platform` | `string` | Platform type (e.g., `twitter`, `facebook`, `instagram`) |
| `items[].fullname` | `string` | Display name of the account                              |
| `items[].username` | `string` | Username or handle                                       |

If no accounts are returned, the user needs to connect social accounts in [Accounts](https://my.blotato.com/accounts).

#### Examples

**List all accounts**

```http
GET https://backend.blotato.com/v2/users/me/accounts HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

**Filter by platform**

```http
GET https://backend.blotato.com/v2/users/me/accounts?platform=instagram HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

### List Subaccounts (Pages)

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/users/me/accounts/:accountId/subaccounts`

**Method:** `GET`

#### Description

Returns subaccounts for a connected account. Subaccounts include Facebook Pages, LinkedIn Company Pages, and YouTube Playlists. Use this to get the `pageId` required for publishing to Facebook/LinkedIn, or `playlistIds` for adding YouTube videos to playlists.

#### Path Parameters

| Field       | Type     | Required | Description                                    |
| ----------- | -------- | -------- | ---------------------------------------------- |
| `accountId` | `string` | Yes      | The account ID from the List Accounts endpoint |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "123456789",
      "accountId": "98433",
      "name": "My Business Page"
    }
  ]
}
```

| Field               | Type     | Description                                                                         |
| ------------------- | -------- | ----------------------------------------------------------------------------------- |
| `items`             | `array`  | List of subaccounts                                                                 |
| `items[].id`        | `string` | Subaccount ID. Use this as `target.pageId` when publishing to Facebook or LinkedIn. |
| `items[].accountId` | `string` | Parent account ID                                                                   |
| `items[].name`      | `string` | Name of the page                                                                    |

If subaccounts is empty, the user needs to connect a Facebook Page, LinkedIn Company Page, or YouTube account in [Accounts](https://my.blotato.com/accounts).

#### Example

```http
GET https://backend.blotato.com/v2/users/me/accounts/98433/subaccounts HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

### List Available LinkedIn Pages

Use `GET /v2/integrations/oauth2/linkedin/pages?accountId=ACCOUNT_ID` to list organization pages the connected LinkedIn account administers. `accountId` is required and comes from List Connected Accounts.

The response contains `items`, each with `id`, `name`, and `isActive`. `isActive: true` means Blotato is already connected to the page. This endpoint discovers available pages. For publishing, retrieve connected page IDs through List Subaccounts above.

```http
GET https://backend.blotato.com/v2/integrations/oauth2/linkedin/pages?accountId=98434 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

Connect missing pages in [Accounts](https://my.blotato.com/accounts), then repeat the subaccounts lookup. A `422` response for an expired connection requires reconnection. The route limit is 20 requests per minute.

### How to Get the Right IDs for Publishing

Different platforms need different IDs. Here is how to get them for each platform.

#### Twitter, Instagram, TikTok, Threads, Bluesky

These platforms need only an `accountId`:

1. Call `GET /v2/users/me/accounts?platform=twitter` (replace with your platform)
2. Use `items[].id` as `accountId` in your publish request
3. If multiple accounts are returned, use `fullname` or `username` to identify the correct one, or ask the user which account to use

#### YouTube

YouTube needs an `accountId`. To add videos to playlists, also fetch subaccounts to get playlist IDs:

1. Call `GET /v2/users/me/accounts?platform=youtube`
2. Use `items[].id` as `accountId`
3. Call `GET /v2/users/me/accounts/{accountId}/subaccounts`
4. Use `items[].id` values as `target.playlistIds` in your publish request
5. `playlistIds` is optional -- omit it to publish without adding to a playlist

#### Facebook

Facebook requires both `accountId` and `pageId`:

1. Call `GET /v2/users/me/accounts?platform=facebook`
2. Use `items[].id` as `accountId`
3. Call `GET /v2/users/me/accounts/{accountId}/subaccounts`
4. Use `items[].id` as `target.pageId` in your publish request
5. If multiple pages are returned, use `name` to identify the correct one, or ask the user which page to use

**Full example:**

```
1. GET /v2/users/me/accounts?platform=facebook
   Response: { "items": [{ "id": "98433", "platform": "facebook", ... }] }

2. GET /v2/users/me/accounts/98433/subaccounts
   Response: { "items": [{ "id": "123456789", "name": "My Business Page" }] }

3. POST /v2/posts with:
   {
     "post": {
       "accountId": "98433",
       "content": { "text": "Hello!", "mediaUrls": [], "platform": "facebook" },
       "target": { "targetType": "facebook", "pageId": "123456789" }
     }
   }
```

#### LinkedIn Company Page

To post to a LinkedIn Company Page instead of your personal profile:

1. Call `GET /v2/users/me/accounts?platform=linkedin`
2. Use `items[].id` as `accountId`
3. Call `GET /v2/users/me/accounts/{accountId}/subaccounts`
4. Use `items[].id` as `target.pageId`
5. If you skip `pageId`, the post goes to your personal LinkedIn profile

#### Pinterest

Pinterest requires a `boardId`. Fetch boards with the List Pinterest Boards endpoint:

1. Call `GET /v2/users/me/accounts?platform=pinterest`
2. Use `items[].id` as `accountId`
3. Call `GET /v2/social/pinterest/boards?accountId={accountId}`
4. Use `items[].id` as `target.boardId` in your publish request
5. If multiple boards are returned, use `name` to identify the correct one, or ask the user which board to use

> Web app shortcut: you can also grab a Board ID without the API. Go to [Accounts](https://my.blotato.com/accounts), scroll to your Pinterest account, and select a Board -- Blotato copies its Board ID to your clipboard. Use this if the boards endpoint returns an empty list.

**Full example:**

```
1. GET /v2/users/me/accounts?platform=pinterest
   Response: { "items": [{ "id": "98436", "platform": "pinterest", ... }] }

2. GET /v2/social/pinterest/boards?accountId=98436
   Response: { "items": [{ "id": "1234567890123456789", "name": "Summer Outfits" }] }

3. POST /v2/posts with:
   {
     "post": {
       "accountId": "98436",
       "content": { "text": "Check out this pin!", "mediaUrls": ["https://example.com/image.jpg"], "platform": "pinterest" },
       "target": { "targetType": "pinterest", "boardId": "1234567890123456789" }
     }
   }
```

***

### List Pinterest Boards

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/social/pinterest/boards`

**Method:** `GET`

#### Description

Returns boards owned by a connected Pinterest account. Use this to get the `boardId` required when publishing a pin.

#### Query Parameters

| Field       | Type     | Required | Description                                                 |
| ----------- | -------- | -------- | ----------------------------------------------------------- |
| `accountId` | `string` | Yes      | Blotato Pinterest account ID from `GET /users/me/accounts`. |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "1234567890123456789",
      "name": "Summer Outfits"
    }
  ]
}
```

| Field          | Type     | Description                                                   |
| -------------- | -------- | ------------------------------------------------------------- |
| `items`        | `array`  | List of Pinterest boards (max 250)                            |
| `items[].id`   | `string` | Board ID. Use this as `target.boardId` when publishing a pin. |
| `items[].name` | `string` | Display name of the board on Pinterest                        |

#### Example

```http
GET https://backend.blotato.com/v2/social/pinterest/boards?accountId=98436 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

## Credits /v2/credits

Blotato credits pay for AI generations such as videos and images. Use these endpoints to check your remaining credit balance, see current pricing, and buy more credits. Two endpoints:

* [Get Credit Balance](#api-api-reference-credits--get-credit-balance) — `GET /v2/credits`
* [Buy Credits](#api-api-reference-credits--buy-credits) — `POST /v2/credits`

Credits belong to a single account and are non-transferable. Confirm the account email from [Get Credit Balance](#api-api-reference-credits--get-credit-balance) before you buy, so the credits land on the account you intend.

***

### Get Credit Balance

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/credits`

**Method:** `GET`

#### Description

Returns your remaining credits, the email of the account the API key belongs to, and current purchase pricing.

#### Response

**Status Code:** `200 OK`

| Field                    | Type      | Description                                                        |
| ------------------------ | --------- | ------------------------------------------------------------------ |
| `creditsRemaining`       | `integer` | Credits left on the account.                                       |
| `accountEmail`           | `string`  | Email of the account the API key belongs to.                       |
| `purchaseQuantityRange`  | `object`  | Allowed purchase range, with `min` and `max` credits per purchase. |
| `pricePer1000CreditsUsd` | `number`  | Price in US dollars per 1,000 credits.                             |

Example response:

```json
{
  "creditsRemaining": 54688,
  "accountEmail": "user@example.com",
  "purchaseQuantityRange": { "min": 1000, "max": 10000 },
  "pricePer1000CreditsUsd": 6.0
}
```

#### Errors

| Status | Reason                      |
| ------ | --------------------------- |
| `401`  | Missing or invalid API key. |
| `500`  | Server error.               |

***

### Buy Credits

#### Endpoint

**URL:** `/credits`

**Method:** `POST`

#### Description

Creates a Stripe Checkout link to buy credits. Creating the link does not charge anything. The account owner opens the link in a browser and completes payment there, and the purchased credits land on this account. Stripe creates an invoice for the credit purchase after checkout succeeds. Buy between 1,000 and 10,000 credits per request.

Confirm the account email from [Get Credit Balance](#api-api-reference-credits--get-credit-balance) first, since credits are non-transferable between accounts.

#### Request Body

| Field      | Type      | Required | Description                               |
| ---------- | --------- | -------- | ----------------------------------------- |
| `quantity` | `integer` | Yes      | Credits to buy. Between 1,000 and 10,000. |
| `referral` | `string`  | No       | Optional referral code.                   |

#### Response

**Status Code:** `201 Created`

| Field         | Type     | Description                                                                                                |
| ------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `checkoutUrl` | `string` | Stripe Checkout URL. Open it in a browser to complete payment. No charge happens until checkout completes. |

Example response:

```json
{
  "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_test_123"
}
```

#### Errors

| Status | Reason                                                                             |
| ------ | ---------------------------------------------------------------------------------- |
| `400`  | The quantity is outside the 1,000 to 10,000 range, or the request body is invalid. |
| `401`  | Missing or invalid API key.                                                        |
| `429`  | Rate limit exceeded (10 requests per hour).                                        |
| `500`  | Server error.                                                                      |

***

### Pricing

Credits cost $6.00 per 1,000 credits. Read the current price from [Get Credit Balance](#api-api-reference-credits--get-credit-balance) (`pricePer1000CreditsUsd`), since pricing changes over time.

***

## Upload Media /v2/media

### Upload is Now Optional

You no longer need to upload media to Blotato before publishing. You can pass any publicly accessible image/video URL directly into the `mediaUrls` parameter in the Publish node. Blotato handles the media transfer automatically.

For local files without a public URL, use the [Presigned Upload](#api-api-reference-upload-media-v2-media--presigned-upload-local-files) endpoint below. This lets you upload directly to Blotato without needing Google Drive, S3, or any other intermediate storage. Upload size depends on your plan (see [Plan Limits](/settings/billing-and-credits.md#plan-limits)).

The legacy Upload Media endpoint is still available if you prefer to use it, or if you need to host media on Blotato's servers.

***

### Upload Media

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/media`

**Method:** `POST`

#### Description

This endpoint allows users to upload media by providing a URL. The uploaded media will be processed and stored, returning a new media URL that is used to publish a new post. Most of the platforms require validated URLs for posting images.

You can upload:

* publicly accessible URLs
* base64 encoded image data

Media uploads are subject to your plan's upload size limit. See [Plan Limits](/settings/billing-and-credits.md#plan-limits) for more details.

If you are using the n8n "Binary Data" upload option, the file size limit is 15MB. For larger files, use the [Presigned Upload](#api-api-reference-upload-media-v2-media--presigned-upload-local-files) endpoint above (no external hosting needed), or host your media on Google Drive, AWS S3, or another cloud storage service and pass the URL instead.

#### Request

**Request Body**

| Field | Type     | Required | Description                     |
| ----- | -------- | -------- | ------------------------------- |
| `url` | `string` | ✅        | The URL of the media to upload. |

#### Responses

**Success Response**

**Status Code:** `201 Created`

**Response Body:**

```
{
  "url": "https://database.blotato.io/path-to-uploaded-media.jpg"
}
```

**Error Responses**

**Internal Server Error**

**Status Code:** `500 Internal Server Error`

```
{
  "code": 9999,
  "message": "An unknown error occurred."
}
```

**Too Many Requests**

Media upload has a user-level rate limit of 30 requests / minute.

**Status Code:** `429 Too many requests`

```
{
  "statusCode": 429,
  "message": "Rate limit exceeded, retry in 49 seconds"
}
```

**Error Codes**

The following client error codes may be returned:

| Code    | Description                                                                                                   |
| ------- | ------------------------------------------------------------------------------------------------------------- |
| `90009` | Blotato could not fetch the media URL. Confirm the URL loads or downloads without signing in, then try again. |
| `9999`  | Unknown error.                                                                                                |

#### Examples

**1. Upload Media**

```
POST https://backend.blotato.com/v2/media HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "url": "https://example.com/image.jpg"
}
```

**Response:**

```
{
  "url": "https://database.blotato.io/d1655c49-0bc4-4dd0-88b2-323ce0069fa4.jpg"
}
```

### Presigned Upload (Local Files)

Upload local files directly to Blotato without hosting them on Google Drive or S3 first. This is the recommended approach for MCP users and anyone working with local files.

#### Endpoint

**URL:** `/media/uploads`

**Method:** `POST`

**Rate limit:** 120 requests per minute

#### Request

**Request Body**

| Field      | Type     | Required | Description                                                                               |
| ---------- | -------- | -------- | ----------------------------------------------------------------------------------------- |
| `filename` | `string` | Yes      | Filename with extension (e.g., "photo.jpg", "video.mp4"). Used to determine content type. |

#### Response

**Status Code:** `201 Created`

```json
{
  "presignedUrl": "https://...",
  "publicUrl": "https://..."
}
```

* `presignedUrl` -- upload your file here via HTTP PUT. Expires after a short period.
* `publicUrl` -- the final public URL to use in `mediaUrls` when publishing.

#### Complete Example: Upload a Local File and Post It

Three steps: get the presigned URL, upload the file, then publish the post.

**Step 1: Get a presigned upload URL**

```bash
curl -X POST https://backend.blotato.com/v2/media/uploads \
  -H "Content-Type: application/json" \
  -H "blotato-api-key: YOUR_API_KEY" \
  -d '{"filename": "product-photo.jpg"}'
```

Response:

```json
{
  "presignedUrl": "https://database.blotato.io/storage/v1/object/upload/sign/...",
  "publicUrl": "https://database.blotato.io/storage/v1/object/public/.../product-photo.jpg"
}
```

**Step 2: Upload the file to the presigned URL**

```bash
curl -X PUT "PRESIGNED_URL_FROM_STEP_1" \
  -H "Content-Type: image/jpeg" \
  --data-binary @product-photo.jpg
```

Use the correct Content-Type for your file (image/jpeg, image/png, video/mp4, etc.).

**Step 3: Publish a post using the public URL**

```bash
curl -X POST https://backend.blotato.com/v2/posts \
  -H "Content-Type: application/json" \
  -H "blotato-api-key: YOUR_API_KEY" \
  -d '{
    "post": {
      "accountId": "YOUR_ACCOUNT_ID",
      "content": {
        "text": "Check out this product!",
        "mediaUrls": ["PUBLIC_URL_FROM_STEP_1"],
        "platform": "instagram"
      },
      "target": { "targetType": "instagram" }
    }
  }'
```

Max file size depends on your plan. See: [Plan Limits](/settings/billing-and-credits.md#plan-limits) for more details. The presigned URL expires after a short period -- upload the file immediately after receiving it.

#### Troubleshooting: presigned PUT fails in Claude Cowork or Claude Desktop

If `POST /v2/media/uploads` returns a `presignedUrl` but the **PUT step** to that URL fails with a sandbox, proxy, network egress, or "Stream closed" error, this is a Claude Cowork / Claude Desktop sandbox restriction, not a Blotato API issue.

Fix it by allowlisting `database.blotato.io` in Cowork's network egress settings. Full steps: [Presigned upload PUT fails / "Sandbox error" / network egress blocked](/start-with-an-ai-agent/mcp/faqs.md#claude-cant-upload-my-photos-or-files-to-blotato--sandbox-error--network-egress-blocked-in-claude-cowork-claude-desktop-or-claude-code).

For Claude Code (sandbox not user-configurable), skip the local upload entirely and pass a public URL into `mediaUrls` directly.

***

### Using Google Drive as a Media Source

Google Drive share links open a viewer page, not a direct file. Blotato needs a direct file URL to fetch and read media.

#### Set sharing permissions

For automation workflows (n8n, Make.com), set the entire Google Drive **folder** to "Anyone with the link" as Viewer. All files inside that folder inherit the same permission, so you do not need to set permissions on each file individually.

For one-off uploads, set the individual file to "Anyone with the link" as Viewer.

#### Common Google Drive URL Errors

**Folder URLs don't work**: If your link looks like `/drive/folders/...`, it's a folder link. Blotato needs a direct link to a specific file, not a folder.

**Preview URLs don't work reliably**: If your link ends with `/view?...`, it points to Google Drive's preview page, not the raw file.

#### Use the direct download URL format

For Google Drive files, including large videos, first replace the standard share link with this format:

```
https://drive.usercontent.google.com/download?id={FILE_ID}&export=download&confirm=t
```

Replace `{FILE_ID}` with the ID from your share link. For example, if your share link is:

```
https://drive.google.com/file/d/1aBcDeFgHiJkLmNoPqRsTuVwXyZ/view
```

The file ID is `1aBcDeFgHiJkLmNoPqRsTuVwXyZ`, so the direct URL is:

```
https://drive.usercontent.google.com/download?id=1aBcDeFgHiJkLmNoPqRsTuVwXyZ&export=download&confirm=t
```

Make sure the file is shared with "Anyone with the link" as Viewer.

The `confirm=t` parameter requests the file download from Google Drive. Blotato does not impose a 100 MB Google Drive cutoff. The file still must fit your Blotato plan's upload limit and the destination platform's media limit.

#### Seeing error "Google Drive can't scan this file for viruses"?

Google Drive sometimes returns a virus-scan warning page instead of the file for large videos. Blotato converts Google Drive share links to the direct download format above, but Google Drive might still return the warning page.

If the direct download URL still returns the warning page, use the [Presigned Upload](#api-api-reference-upload-media-v2-media--presigned-upload-local-files) flow, frame.io, AWS S3, or Google Cloud Storage. These options avoid the Google Drive warning page.

***

## Publish Post /v2/posts

### Publishing a Post

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/posts`

**Method:** `POST`

**Rate Limit:** 30 requests / minute

#### Description

Publish a new post to a social media platform. The request must include the post content, target platform, and an `accountId` (fetched from `GET /v2/users/me/accounts`).

After submitting, poll [Get Post Status](#api-api-reference-get-post) with the returned `postSubmissionId` to track publishing progress.

#### Before You Start

1. Fetch your connected accounts: `GET /v2/users/me/accounts` ([docs](#api-api-reference-accounts))
2. Fetch your subaccounts to get `pageId` for Facebook/LinkedIn and `playlistIds` for YouTube: `GET /v2/users/me/accounts/{accountId}/subaccounts` ([docs](#api-api-reference-accounts--list-subaccounts-pages)).
3. For a social platform, set `content.platform` and `target.targetType` to the same value (e.g., both `"twitter"`). The webhook target uses `other` and `webhook`.

***

#### Request Body

The request body has two top-level fields: `post` (required) and optional scheduling fields. Do not nest scheduling fields inside `post`.

| Field             | Type      | Required | Description                                                                                                                         |
| ----------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `post`            | `object`  | Yes      | The post content and metadata.                                                                                                      |
| `scheduledTime`   | `string`  | No       | ISO 8601 timestamp with timezone offset for the requested future time. Use this without `useNextFreeSlot: true`.                    |
| `useNextFreeSlot` | `boolean` | No       | Schedule at the next available slot time. Defaults to `false`. Requires an available slot matching the platform, account, and Page. |

**Scheduling behavior:**

* Choose 1 scheduling method. If `scheduledTime` is set, it supplies the publish time. Setting `useNextFreeSlot: true` as well still runs slot validation and fails when no matching slot is available. Do not combine the fields.
* If `useNextFreeSlot` is `true` (and no `scheduledTime`): the post is scheduled at the next available calendar slot matching the destination.
* If neither `scheduledTime` nor `useNextFreeSlot` is provided: **the post publishes immediately.**
* Both fields must be **root-level** (siblings of `post`). If they are nested inside `post`, `options`, or any other object, they are ignored and the post publishes immediately.

#### `post` Object

| Field       | Type     | Required | Description                                                           |
| ----------- | -------- | -------- | --------------------------------------------------------------------- |
| `accountId` | `string` | Yes      | The account ID from `GET /v2/users/me/accounts`.                      |
| `content`   | `object` | Yes      | The content of the post. See `content` below.                         |
| `target`    | `object` | Yes      | The target platform and platform-specific fields. See `target` below. |

#### `content` Object

| Field             | Type               | Required | Description                                                                                                                                                                                                  |
| ----------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `text`            | `string`           | Yes      | The text content of the post.                                                                                                                                                                                |
| `mediaUrls`       | `array of strings` | Yes      | Array of media URLs. Pass any publicly accessible URL -- no upload step required. Pass `[]` for text-only posts.                                                                                             |
| `platform`        | `string`           | Yes      | For social platforms, match `target.targetType`. Use `other` for the webhook target. Values: `twitter`, `linkedin`, `facebook`, `instagram`, `pinterest`, `tiktok`, `threads`, `bluesky`, `youtube`, `other` |
| `additionalPosts` | `array`            | No       | Additional posts for threads (Twitter, Bluesky, Threads). Each has `text` and `mediaUrls`.                                                                                                                   |

`other` is reserved for publishing to a custom webhook. It does not represent another native social platform.

#### `target` Object

The `target` object requires `targetType` and platform-specific fields. Match `content.platform` for social platforms. For a custom webhook, use `content.platform: "other"` with `target.targetType: "webhook"`.

**Quick Reference: Required Fields Per Platform**

| Platform  | `targetType`  | Required Fields                                                                                                          | Optional Fields                                                                                               |
| --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| Twitter   | `"twitter"`   | (none)                                                                                                                   | (none)                                                                                                        |
| LinkedIn  | `"linkedin"`  | (none)                                                                                                                   | `pageId`                                                                                                      |
| Facebook  | `"facebook"`  | `pageId`                                                                                                                 | `mediaType`, `link`, `firstComment`                                                                           |
| Instagram | `"instagram"` | (none)                                                                                                                   | `mediaType`, `altText`, `collaborators`, `coverImageUrl`, `shareToFeed`, `audioName`, `trial`, `firstComment` |
| TikTok    | `"tiktok"`    | `privacyLevel`, `disabledComments`, `disabledDuet`, `disabledStitch`, `isBrandedContent`, `isYourBrand`, `isAiGenerated` | `title`, `autoAddMusic`, `isDraft`, `imageCoverIndex`, `videoCoverTimestamp`                                  |
| Pinterest | `"pinterest"` | `boardId`                                                                                                                | `title`, `altText`, `link`                                                                                    |
| Threads   | `"threads"`   | (none)                                                                                                                   | `replyControl`                                                                                                |
| Bluesky   | `"bluesky"`   | (none)                                                                                                                   | (none)                                                                                                        |
| YouTube   | `"youtube"`   | `title`, `privacyStatus`, `shouldNotifySubscribers`                                                                      | `isMadeForKids`, `containsSyntheticMedia`, `playlistIds`, `thumbnailUrl`                                      |
| Webhook   | `"webhook"`   | `url`                                                                                                                    | (none)                                                                                                        |

***

**Twitter**

| Field        | Type        | Required |
| ------------ | ----------- | -------- |
| `targetType` | `"twitter"` | Yes      |

**LinkedIn**

| Field        | Type         | Required | Description                                                                                                                         |
| ------------ | ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `targetType` | `"linkedin"` | Yes      |                                                                                                                                     |
| `pageId`     | `string`     | No       | LinkedIn Company Page ID from [subaccounts](#api-api-reference-accounts--list-subaccounts-pages). Omit to post to personal profile. |

**Carousels:** Pass 2-10 image URLs (JPG, PNG) in `content.mediaUrls`. Blotato auto-builds a LinkedIn Document carousel (LinkedIn's modern PDF-based carousel format — viewers swipe through pages). Videos cannot be mixed into a carousel.

**Facebook**

| Field          | Type                  | Required        | Description                                                                                                                                                                                                                                                                                                                                        |
| -------------- | --------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `targetType`   | `"facebook"`          | Yes             |                                                                                                                                                                                                                                                                                                                                                    |
| `pageId`       | `string`              | Yes             | Facebook Page ID from [subaccounts](#api-api-reference-accounts--list-subaccounts-pages).                                                                                                                                                                                                                                                          |
| `mediaType`    | `"reel"` or `"story"` | See description | Video posts must use `"reel"` (regular feed videos no longer supported). Set `"story"` for Stories. Omit (default) for text-only or image posts. Stories require one video or image attachment; if more than one is provided, only the first is used. See [Facebook Reel requirements](/tips-and-tricks/social-platform-requirements.md#facebook). |
| `link`         | `string`              | No              | URL to attach as a link preview.                                                                                                                                                                                                                                                                                                                   |
| `firstComment` | `string`              | No              | Auto-posts this text as the first comment right after the post publishes. Up to 8000 characters. Works on feed posts and reels, not Stories.                                                                                                                                                                                                       |

**Instagram**

| Field           | Type                  | Required | Description                                                                                                                                                                                         |
| --------------- | --------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `targetType`    | `"instagram"`         | Yes      |                                                                                                                                                                                                     |
| `mediaType`     | `"reel"` or `"story"` | No       | Default: `"reel"` for videos. Set `"story"` to publish one image or video as an Instagram Story.                                                                                                    |
| `altText`       | `string`              | No       | Alt text for images. Up to 1000 characters.                                                                                                                                                         |
| `collaborators` | `array of strings`    | No       | Instagram handles to tag (max 3). Do not include the @ sign. Single image posts and reels only - **not supported on carousels** (multi-media posts), which will fail to publish.                    |
| `coverImageUrl` | `string`              | No       | Cover image URL for reels. Max 8MB.                                                                                                                                                                 |
| `shareToFeed`   | `boolean`             | No       | Share the reel to the Instagram feed. Only applies to reels.                                                                                                                                        |
| `audioName`     | `string`              | No       | Custom audio name for reels. You can only set this once per reel.                                                                                                                                   |
| `trial`         | `object`              | No       | Settings for trial reels. Trial reels are shown to non-followers first. Only applies to reels. See `trial` object below.                                                                            |
| `firstComment`  | `string`              | No       | Auto-posts this text as the first comment right after the post publishes. Up to 2200 characters. Works on posts, carousels, and reels, not Stories. Useful for putting a link in the first comment. |

Instagram Stories do not support captions through Meta's publishing API. Set the required `content.text` field to an empty string for a Story. To display text, render it into the image or video before publishing. Native Story text, link stickers, and other interactive stickers require manual editing in Instagram. See [Instagram limitations](/social-accounts-and-platform-faqs/instagram/limitations.md#story-captions-and-text-overlays).

**`trial` Object**

| Field                | Type     | Required | Description                                                                                                          |
| -------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `graduationStrategy` | `string` | Yes      | `"MANUAL"` (you promote to followers manually) or `"SS_PERFORMANCE"` (Instagram auto-promotes based on performance). |

**TikTok**

| Field                 | Type       | Required | Description                                                                                                                                                     |
| --------------------- | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `targetType`          | `"tiktok"` | Yes      |                                                                                                                                                                 |
| `privacyLevel`        | `string`   | Yes      | `SELF_ONLY`, `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, or `FOLLOWER_OF_CREATOR`                                                                            |
| `disabledComments`    | `boolean`  | Yes      |                                                                                                                                                                 |
| `disabledDuet`        | `boolean`  | Yes      |                                                                                                                                                                 |
| `disabledStitch`      | `boolean`  | Yes      |                                                                                                                                                                 |
| `isBrandedContent`    | `boolean`  | Yes      | Set `true` only for a paid partnership with a third-party brand. Adds TikTok's paid-partnership disclosure. Set `false` for organic content.                    |
| `isYourBrand`         | `boolean`  | Yes      | Set `true` when the post promotes your own brand, product, or business. This drives TikTok's "Promotional content" disclosure. Set `false` for organic content. |
| `isAiGenerated`       | `boolean`  | Yes      | Set `true` when the content is AI-generated. This drives TikTok's AI-generated label. Set `false` for content you filmed or made yourself.                      |
| `title`               | `string`   | No       | Title for image posts. Max 90 characters. No effect on videos.                                                                                                  |
| `autoAddMusic`        | `boolean`  | No       | Add music to photo posts. No effect on videos. Default: `false`.                                                                                                |
| `isDraft`             | `boolean`  | No       | Post as a draft. Drafts appear in TikTok mobile app notifications, not the Drafts folder.                                                                       |
| `imageCoverIndex`     | `number`   | No       | Index (starting from 0) of image to use as cover for carousels.                                                                                                 |
| `videoCoverTimestamp` | `number`   | No       | Timestamp in milliseconds to use as video cover.                                                                                                                |

For organic content that is not sponsored and not AI-generated, set `isBrandedContent`, `isYourBrand`, and `isAiGenerated` to `false`. Set `isYourBrand` to `true` only to promote your own brand, and `isAiGenerated` to `true` only for AI-generated content. Each field maps to a separate TikTok content disclosure, so set only the ones that apply.

**Pinterest**

| Field        | Type          | Required | Description                                                                                               |
| ------------ | ------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `targetType` | `"pinterest"` | Yes      |                                                                                                           |
| `boardId`    | `string`      | Yes      | Pinterest Board ID. [Get from List Pinterest Boards](#api-api-reference-accounts--list-pinterest-boards). |
| `title`      | `string`      | No       | Pin title.                                                                                                |
| `altText`    | `string`      | No       | Pin alt text.                                                                                             |
| `link`       | `string`      | No       | Pin URL link.                                                                                             |

Pinterest "description" comes from the `content.text` field.

**Threads**

| Field          | Type        | Required | Description                                            |
| -------------- | ----------- | -------- | ------------------------------------------------------ |
| `targetType`   | `"threads"` | Yes      |                                                        |
| `replyControl` | `string`    | No       | `everyone`, `accounts_you_follow`, or `mentioned_only` |

**Bluesky**

| Field        | Type        | Required |
| ------------ | ----------- | -------- |
| `targetType` | `"bluesky"` | Yes      |

**YouTube**

| Field                     | Type               | Required | Description                                                                                                                          |
| ------------------------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `targetType`              | `"youtube"`        | Yes      |                                                                                                                                      |
| `title`                   | `string`           | Yes      | Video title.                                                                                                                         |
| `privacyStatus`           | `string`           | Yes      | `private`, `public`, or `unlisted`                                                                                                   |
| `shouldNotifySubscribers` | `boolean`          | Yes      |                                                                                                                                      |
| `isMadeForKids`           | `boolean`          | No       | Default: `false`                                                                                                                     |
| `containsSyntheticMedia`  | `boolean`          | No       | Whether media contains AI-generated content.                                                                                         |
| `playlistIds`             | `array of strings` | No       | YouTube playlist IDs to add the video to. Get from [subaccounts](#api-api-reference-accounts--list-subaccounts-pages).               |
| `thumbnailUrl`            | `string`           | No       | Publicly accessible image URL to use as the video thumbnail. Requires a verified YouTube account with custom thumbnail capabilities. |

YouTube "description" comes from the `content.text` field. Tags are not supported -- YouTube recommends against relying on tags for discovery ([source](https://support.google.com/youtube/answer/146402?hl=en)).

**Webhook**

This target is available through REST, not `blotato_create_post`. Set `content.platform` to `"other"` and `target.targetType` to `"webhook"`. It sends post content to your URL. It is separate from the DM automation webhook action and does not subscribe you to account-wide events.

| Field        | Type        | Required | Description                            |
| ------------ | ----------- | -------- | -------------------------------------- |
| `targetType` | `"webhook"` | Yes      |                                        |
| `url`        | `string`    | Yes      | The webhook URL to send the post data. |

***

#### Response

**Status Code:** `201 Created`

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "scheduledTime": "2025-03-10T15:30:00Z"
}
```

| Field              | Type     | Description                                                                                                       |
| ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `postSubmissionId` | `string` | ID of the post submission. Use it to poll [Get Post Status](#api-api-reference-get-post) for publishing progress. |
| `scheduledTime`    | `string` | The resolved UTC time the post publishes. Absent when the post publishes immediately.                             |

`scheduledTime` tells you the slot Blotato picked when you send `useNextFreeSlot: true`, so you read the scheduled time without a follow-up call.

Failed posts are visible at <https://my.blotato.com/posts?status=failed>. The most common cause of failed posts is incorrect JSON structure.

***

#### Examples

**1. Simplest Post (Twitter, text only)**

```http
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Hello, world!",
      "mediaUrls": [],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  }
}
```

**2. Instagram Post with Images**

```http
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98434",
    "content": {
      "text": "Check out these photos!",
      "mediaUrls": [
        "https://example.com/image1.jpg",
        "https://example.com/image2.jpg"
      ],
      "platform": "instagram"
    },
    "target": {
      "targetType": "instagram",
      "firstComment": "Full guide: https://blotato.com"
    }
  }
}
```

Posting multiple images to Instagram creates a carousel.

`firstComment` auto-posts as the first comment right after the post goes live, a common way to share a link without putting it in the caption.

**3. Facebook Page Post**

```http
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98433",
    "content": {
      "text": "New product announcement!",
      "mediaUrls": ["https://example.com/product.jpg"],
      "platform": "facebook"
    },
    "target": {
      "targetType": "facebook",
      "pageId": "123456789"
    }
  }
}
```

Get `accountId` and `pageId` from the [Accounts endpoints](#api-api-reference-accounts--facebook).

**4. TikTok Post (all required fields)**

```http
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98435",
    "content": {
      "text": "Tips for productivity",
      "mediaUrls": ["https://example.com/video.mp4"],
      "platform": "tiktok"
    },
    "target": {
      "targetType": "tiktok",
      "privacyLevel": "PUBLIC_TO_EVERYONE",
      "disabledComments": false,
      "disabledDuet": false,
      "disabledStitch": false,
      "isBrandedContent": false,
      "isYourBrand": false,
      "isAiGenerated": true
    }
  }
}
```

**5. Scheduled Post**

```http
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "This will go live later",
      "mediaUrls": [],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  },
  "scheduledTime": "2025-12-25T15:00:00Z"
}
```

**6. Twitter Thread**

```http
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Here is a thread about AI content creation (1/3)",
      "mediaUrls": [],
      "platform": "twitter",
      "additionalPosts": [
        {
          "text": "First, research your topic using reliable sources (2/3)",
          "mediaUrls": []
        },
        {
          "text": "Then, create visuals and publish across platforms (3/3)",
          "mediaUrls": []
        }
      ]
    },
    "target": {
      "targetType": "twitter"
    }
  }
}
```

Threads work for Twitter, Bluesky, and Threads.

**7. Schedule at Next Free Slot**

```http
POST https://backend.blotato.com/v2/posts HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Scheduled to the next free slot",
      "mediaUrls": [],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  },
  "useNextFreeSlot": true
}
```

`useNextFreeSlot` and `scheduledTime` are top-level fields, not inside `post`.

***

## Get Post /v2/posts/:postSubmissionId

### Check Post Status

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/posts/:postSubmissionId`

**Method:** `GET`

**Rate Limit:** 60 requests / minute

#### Description

Poll this endpoint to check the publishing status of a post. After submitting a post with [Create Post](#api-api-reference-publish-post), use the returned `postSubmissionId` to track its progress.

The lookup checks failed, published, and scheduled records belonging to the authenticated Blotato user. If none matches, it returns `in-progress`. This fallback does not confirm a live queue job or validate a guessed submission ID. Use the original create response and account credentials.

#### Request

**Path Parameters**

| Field              | Type     | Required | Description                                                  |
| ------------------ | -------- | -------- | ------------------------------------------------------------ |
| `postSubmissionId` | `string` | Yes      | The post submission ID returned by the Create Post endpoint. |

#### Responses

**Success Response**

**Status Code:** `200 OK`

The response shape depends on the current status:

**Published (terminal - success):**

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "published",
  "publicUrl": "https://x.com/user/status/123456"
}
```

**Failed (terminal - error):**

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "failed",
  "errorMessage": "Unsupported media type"
}
```

**In Progress (keep polling):**

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "in-progress"
}
```

**Scheduled (queued for a future time):**

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "scheduled",
  "scheduledTime": "2025-03-10T15:30:00Z"
}
```

**Response Keys**

| Field              | Type     | Description                                                                 |
| ------------------ | -------- | --------------------------------------------------------------------------- |
| `postSubmissionId` | `string` | The post submission ID.                                                     |
| `status`           | `string` | One of: `in-progress`, `scheduled`, `published`, `failed`.                  |
| `scheduledTime`    | `string` | The scheduled publish time. Present when status is `scheduled`.             |
| `publicUrl`        | `string` | URL returned for a published result, when available. The field is optional. |
| `errorMessage`     | `string` | Description of the failure. Present when status is `failed`.                |

#### Polling Strategy

1. Wait at least 10 seconds between status requests.
2. Continue while status is `in-progress`, within a defined attempt or elapsed-time limit. Verify the ID came from the original create response.
3. If status is `scheduled`, report the returned `scheduledTime` and stop this polling loop. Scheduling is confirmed, not publication.
4. Stop when status is `published` (success) or `failed` (error).
5. On `failed`, inspect `errorMessage` and the destination before deciding whether a corrected or delayed retry is appropriate. Do not repeat a create request blindly.

If the result remains unresolved, retain the ID and follow [publishing-status diagnosis](#support-publishing-status). A polling deadline is not proof of failure.

Failed posts are also visible at <https://my.blotato.com/posts?status=failed>.

#### Example

```http
GET https://backend.blotato.com/v2/posts/a1b2c3d4-e5f6-7890-abcd-ef1234567890 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

## List Posts /v2/posts

### List Posts

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/posts`

**Method:** `GET`

**Rate Limit:** 60 requests / minute

#### Description

Returns the current user's posts (scheduled, published, and failed) within a time window, ordered by post time (most recent first). Supports cursor-based pagination and filtering by status and platform.

To check the status of a single post by its `postSubmissionId`, use [Get Post Status](#api-api-reference-get-post) instead.

#### Query Parameters

| Field      | Type       | Required | Description                                                                                                                                                                                                                         |
| ---------- | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `since`    | `string`   | No       | Only include posts whose post time is on or after this ISO 8601 timestamp. Defaults to 7 days ago.                                                                                                                                  |
| `until`    | `string`   | No       | Only include posts whose post time is on or before this ISO 8601 timestamp. Defaults to 7 days from now.                                                                                                                            |
| `limit`    | `integer`  | No       | Number of items per page. Min: 1, Max: 250. Default: 50.                                                                                                                                                                            |
| `cursor`   | `string`   | No       | Pagination cursor from a previous response. Pass it to fetch the next page.                                                                                                                                                         |
| `status`   | `string[]` | No       | Filter by status. One or more of `scheduled`, `published`, `failed`. Pass multiple values to include any of them. Omit to include all statuses.                                                                                     |
| `platform` | `string[]` | No       | Filter by social media platform. One or more of `twitter`, `instagram`, `linkedin`, `facebook`, `tiktok`, `pinterest`, `threads`, `bluesky`, `youtube`. Pass multiple values to include any of them. Omit to include all platforms. |

Array query parameters are repeated, e.g. `?status=scheduled&status=published`.

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "98432",
      "postTime": "2026-04-01T14:00:00.000Z",
      "platform": "twitter",
      "text": "Scheduled post content",
      "mediaUrls": [],
      "account": { "id": "123", "name": "Blotato", "subaccount": null },
      "state": {
        "type": "scheduled"
      }
    },
    {
      "id": "98431",
      "postTime": "2026-03-30T09:15:00.000Z",
      "platform": "instagram",
      "text": "Check out this image!",
      "mediaUrls": ["https://example.com/image1.jpg"],
      "account": { "id": "456", "name": "Blotato", "subaccount": null },
      "state": {
        "type": "published",
        "postUrl": "https://instagram.com/p/abc123",
        "analytics": {
          "latest": {
            "fetchedAt": "2026-09-30T12:00:00Z",
            "metrics": { "viewsCount": "18400", "likesCount": "920" }
          },
          "history": []
        }
      }
    },
    {
      "id": "98430",
      "postTime": "2026-03-29T11:00:00.000Z",
      "platform": "tiktok",
      "text": "Failed upload",
      "mediaUrls": ["https://example.com/clip.mp4"],
      "account": null,
      "state": {
        "type": "failed",
        "errorMessage": "Unsupported media type"
      }
    }
  ],
  "cursor": "MjAyNi0wMy0yOVQxMTowMDow..."
}
```

**Response Keys**

| Field               | Type             | Description                                                                                                                                                                                |
| ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `items`             | `array`          | List of posts within the time window, ordered by `postTime` descending.                                                                                                                    |
| `items[].id`        | `string`         | The post's identifier. Distinct per `state.type`: scheduled posts use the schedule ID, published posts use the published post ID, failed posts use the failed post ID.                     |
| `items[].postTime`  | `string`         | ISO 8601 UTC timestamp. For scheduled posts this is the planned publish time; for published posts it is when the post was published; for failed posts it is when the failure was recorded. |
| `items[].platform`  | `string`         | Social media platform.                                                                                                                                                                     |
| `items[].text`      | `string`         | The post text.                                                                                                                                                                             |
| `items[].mediaUrls` | `string[]`       | URLs of attached media (images, video). Empty when text-only.                                                                                                                              |
| `items[].account`   | `object or null` | Connected account with `id`, `name`, and `subaccount`. Null if the account is no longer connected.                                                                                         |
| `items[].state`     | `object`         | Discriminated union; see below.                                                                                                                                                            |
| `cursor`            | `string`         | Pagination cursor. Pass this as the `cursor` query parameter to fetch the next page. Absent when there are no more pages.                                                                  |

**`state` object**

The shape depends on `state.type`:

**Scheduled**

```json
{ "type": "scheduled" }
```

**Published**

```json
{ "type": "published", "postUrl": "https://instagram.com/p/abc123", "analytics": null }
```

| Field       | Type             | Description                                                                                                                                            |
| ----------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `postUrl`   | `string \| null` | The live URL of the published post. Null if the platform did not return one.                                                                           |
| `analytics` | `object \| null` | Latest snapshot under `latest` and every snapshot, oldest first, under `history`. Null if metrics have not been collected or analytics is unavailable. |

**Failed**

```json
{ "type": "failed", "errorMessage": "Unsupported media type" }
```

| Field          | Type             | Description                                |
| -------------- | ---------------- | ------------------------------------------ |
| `errorMessage` | `string \| null` | Human-readable description of the failure. |

#### Errors

| Status | Reason                                       |
| ------ | -------------------------------------------- |
| `422`  | Invalid `since`, `until`, or `cursor` value. |

#### Examples

**Default request**

```http
GET https://backend.blotato.com/v2/posts HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

**Only published Twitter posts in the last 30 days**

```http
GET https://backend.blotato.com/v2/posts?since=2026-04-12T00:00:00Z&status=published&platform=twitter HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

**Scheduled or published, multi-platform**

```http
GET https://backend.blotato.com/v2/posts?status=scheduled&status=published&platform=twitter&platform=instagram HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

**Walking pages**

```http
GET https://backend.blotato.com/v2/posts?limit=100 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

If the response contains a `cursor`, pass it on the next request:

```http
GET https://backend.blotato.com/v2/posts?limit=100&cursor=MjAyNi0wMy0yOVQxMTowMDow... HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

When the response omits `cursor`, you have reached the end of the list.

***

## List Published Posts /v2/published-posts

List your published posts, filtered by text and platform, with each post's latest analytics snapshot attached. Use this to find a published post and its `id`, or to browse recent posts with their current metrics.

For engagement rankings and full metric history, see [Analytics](#api-analytics).

### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/published-posts`

**Method:** `GET`

### Description

Returns your published posts, newest or oldest first, filtered by an optional full-text query and platform. Each item carries its latest metrics snapshot. Results use offset-based pagination with a total `count`.

### Query Parameters

| Field      | Type      | Required | Description                                                    |
| ---------- | --------- | -------- | -------------------------------------------------------------- |
| `query`    | `string`  | No       | Full-text search over post content. Omit to include all posts. |
| `platform` | `string`  | No       | Filter to one platform. Omit to include all platforms.         |
| `sortBy`   | `string`  | No       | Sort order: `newest` or `oldest`. Default `newest`.            |
| `offset`   | `integer` | No       | Number of posts to skip before the page. Default 0.            |
| `limit`    | `integer` | No       | Number of posts to return. Min: 1, Max: 100. Default: 20.      |

### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "98431",
      "content": "Check out this image!",
      "postUrl": "https://instagram.com/p/abc123",
      "platform": "instagram",
      "createdAt": "2026-05-30T09:15:00.000Z",
      "mediaUrls": ["https://example.com/image1.jpg"],
      "latestMetrics": {
        "fetchedAt": "2026-06-11T12:00:00.000Z",
        "metrics": { "likesCount": "1240", "reachCount": "53000" }
      },
      "metricsHistory": []
    }
  ],
  "count": 137
}
```

| Field                    | Type             | Description                                                                                                             |
| ------------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `items`                  | `array`          | Published posts for this page.                                                                                          |
| `items[].id`             | `string`         | The published post id. Pass it to [Get Post Analytics](#api-analytics--get-post-analytics) for full metric history.     |
| `items[].content`        | `string`         | The post text.                                                                                                          |
| `items[].postUrl`        | `string or null` | Live URL of the published post. Null if the platform did not return one.                                                |
| `items[].platform`       | `string`         | Social media platform.                                                                                                  |
| `items[].createdAt`      | `string`         | ISO 8601 timestamp when the post was published.                                                                         |
| `items[].mediaUrls`      | `string[]`       | URLs of attached media. Empty when text-only.                                                                           |
| `items[].latestMetrics`  | `object`         | The most recent metrics snapshot. Absent if no metrics have been collected yet. See [Metrics](#api-analytics--metrics). |
| `items[].metricsHistory` | `array`          | Collected snapshots, oldest first. Each is `{ fetchedAt, metrics }`.                                                    |
| `count`                  | `integer`        | Total number of posts matching your search, across all pages.                                                           |

To page through results, increase `offset` by `limit` on each call until `offset` reaches `count`.

### Errors

| Status | Reason                                        |
| ------ | --------------------------------------------- |
| `422`  | Invalid `sortBy`, `offset`, or `limit` value. |

### Example

```http
GET https://backend.blotato.com/v2/published-posts?query=summer&platform=instagram&limit=20 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

### Get a Published Post

**URL:** `/published-posts/:id`

**Method:** `GET`

Returns 1 published post by its Blotato published-post ID.

```json
{
  "publishedPost": {
    "id": "98431",
    "platform": "instagram",
    "content": "Check out this image!",
    "mediaUrls": ["https://example.com/image1.jpg"],
    "postUrl": "https://instagram.com/p/abc123",
    "createdAt": "2026-05-30T09:15:00.000Z",
    "rawPost": null
  }
}
```

`rawPost` contains the original publish payload for posts published through Blotato and is null for other posts. The endpoint returns `404` when the ID does not exist or does not belong to the authenticated user.

***

## Analytics /v2/analytics

Blotato collects engagement analytics for your published posts on 8 platforms: Twitter/X, Instagram, Facebook, Threads, Bluesky, TikTok, YouTube, and Pinterest. Analytics for LinkedIn are not available yet. Four read-only endpoints expose this data:

* [List Top Performing Posts](#api-analytics--list-top-performing-posts) — `GET /v2/analytics`
* [List Performance by Platform](#api-analytics--list-performance-by-platform) — `GET /v2/analytics/platforms`
* [Get Analytics Summary](#api-analytics--get-analytics-summary) — `GET /v2/analytics/summary`
* [Get Post Analytics](#api-analytics--get-post-analytics) — `GET /v2/posts/{id}/analytics`

Analytics is available on all paid plans, both in the [Analytics dashboard](https://my.blotato.com/analytics) and via the API documented below. Fair usage limits may apply in the future.

For a web-app and AI-agent walkthrough, see [Analytics](/web-app-features/analytics.md).

Both endpoints return the latest snapshot Blotato has already collected. They do not trigger a fresh fetch from the social platform.

#### Using analytics from Make.com or n8n

The official Blotato Make.com module does not include analytics yet. To pull analytics from a Make scenario, call the two endpoints on this page with Make's HTTP module, passing your API key in the `blotato-api-key` header. The same approach works in n8n with the HTTP Request node.

#### Collection schedule

Blotato collects a snapshot of each post's metrics at fixed checkpoints, measured from when the post publishes. Higher plans collect more often and keep collecting longer.

| Plan    | Checkpoints per post | Collection window | Cadence                                                                        |
| ------- | :------------------: | ----------------- | ------------------------------------------------------------------------------ |
| Starter |           4          | 30 days           | Day 1, then days 7, 14, and 30                                                 |
| Creator |          18          | 30 days           | 2 hours, daily through day 8, then every 2-3 days through day 30               |
| Agency  |          20          | 90 days           | 2 and 6 hours, daily through day 8, every 2-3 days through day 30, then day 90 |

Full checkpoint list per plan:

* **Starter:** 1d, 7d, 14d, 30d
* **Creator:** 2h, 1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 10d, 12d, 14d, 17d, 20d, 22d, 24d, 27d, 30d
* **Agency:** 2h, 6h, 1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 10d, 12d, 14d, 17d, 20d, 22d, 24d, 27d, 30d, 90d

Each checkpoint adds a small random offset to spread out collection, so exact timing varies by a few minutes on the early checkpoints and up to a day on the 30-day and 90-day ones. After the final checkpoint, Blotato stops collecting and the last snapshot stays as the post's metrics.

#### Posts stuck on "Analytics pending"

"Analytics pending" means no metric history is available. It does not identify the cause.

1. Check the platform and plan against the supported metrics.
2. Check whether the post was published through Blotato with analytics enabled.
3. Compare the post's age with its plan's checkpoints.
4. Inspect `lastError` and the account's connection state. Reconnect when authorization is missing or expired, not as a repeated generic fix.
5. If the expected checkpoint has passed and no cause is established, give support the published post ID, publication time, platform, plan, last fetch time, and error.

Reconnection does not guarantee a 24-48-hour backfill or a restart of every historical post's schedule. See [new connections and pending data](/web-app-features/analytics.md#i-just-connected-an-account-when-will-analytics-appear).

#### YouTube view counts trail the public count

For YouTube, Blotato reads the YouTube Analytics API, which reports only finalized views after YouTube filters spam and invalid traffic. The count on the YouTube watch page updates in near real time and includes views YouTube has not validated yet, so it reads higher. The two numbers are not expected to match, and the gap narrows as YouTube finalizes more data.

See: [Why is my YouTube view count in Blotato lower than on YouTube?](/support/faqs.md#why-is-my-youtube-view-count-in-blotato-lower-than-on-youtube)

***

### List Top Performing Posts

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/analytics`

**Method:** `GET`

#### Description

Returns your top performing published posts, ordered by the requested metric over a time range. Each item includes its latest metrics snapshot and full snapshot history.

#### Query Parameters

| Field      | Type      | Required | Description                                                                                                                                                                                                                                                                   |
| ---------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `since`    | `string`  | No       | Only include posts published on or after this ISO 8601 timestamp. Defaults to 30 days ago.                                                                                                                                                                                    |
| `until`    | `string`  | No       | Only include posts published on or before this ISO 8601 timestamp. Defaults to now.                                                                                                                                                                                           |
| `platform` | `string`  | No       | Filter to one platform: `twitter`, `instagram`, `facebook`, `threads`, `bluesky`, `tiktok`, `youtube`, or `pinterest`. Omit to include all platforms. Analytics for `linkedin` are not available yet, so filtering to it returns no metrics.                                  |
| `sortBy`   | `string`  | No       | Metric to rank by. One of `likes_count`, `comments_count`, `views_count`, `reach_count`. Default: `views_count`. For Twitter/X, `comments_count` uses `replies_count` when comments are not reported, and `views_count` uses `impressions_count` when views are not reported. |
| `limit`    | `integer` | No       | Number of posts to return. Min: 1, Max: 100. Default: 20.                                                                                                                                                                                                                     |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "98431",
      "content": "Check out this image!",
      "postUrl": "https://instagram.com/p/abc123",
      "platform": "instagram",
      "createdAt": "2026-05-30T09:15:00.000Z",
      "mediaUrls": ["https://example.com/image1.jpg"],
      "latestMetrics": {
        "fetchedAt": "2026-06-11T12:00:00.000Z",
        "metrics": {
          "likesCount": "1240",
          "commentsCount": "85",
          "reachCount": "53000",
          "savesCount": "310"
        }
      },
      "metricsHistory": [
        {
          "fetchedAt": "2026-06-10T12:00:00.000Z",
          "metrics": { "likesCount": "1190", "reachCount": "51200" }
        },
        {
          "fetchedAt": "2026-06-11T12:00:00.000Z",
          "metrics": { "likesCount": "1240", "reachCount": "53000" }
        }
      ]
    }
  ]
}
```

**Response Keys**

| Field                    | Type             | Description                                                                                                                                                                               |
| ------------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`                  | `array`          | Published posts, ordered by the requested `sortBy` metric (highest first). Twitter/X rankings use `replies_count` as the comments fallback and `impressions_count` as the views fallback. |
| `items[].id`             | `string`         | The published post id. Pass this to [Get Post Analytics](#api-analytics--get-post-analytics).                                                                                             |
| `items[].content`        | `string`         | The post text.                                                                                                                                                                            |
| `items[].postUrl`        | `string \| null` | Live URL of the published post. Null if the platform did not return one.                                                                                                                  |
| `items[].platform`       | `string`         | Social media platform.                                                                                                                                                                    |
| `items[].createdAt`      | `string`         | ISO 8601 timestamp when the post was published.                                                                                                                                           |
| `items[].mediaUrls`      | `string[]`       | URLs of attached media. Empty when text-only.                                                                                                                                             |
| `items[].latestMetrics`  | `object`         | The most recent metrics snapshot. Absent if no metrics have been collected yet. See [Metrics](#api-analytics--metrics).                                                                   |
| `items[].metricsHistory` | `array`          | All collected snapshots, oldest first. Each is `{ fetchedAt, metrics }`.                                                                                                                  |

#### Errors

| Status | Reason                                                |
| ------ | ----------------------------------------------------- |
| `422`  | Invalid `since`, `until`, `sortBy`, or `limit` value. |

#### Example

```http
GET https://backend.blotato.com/v2/analytics?sortBy=views_count&platform=instagram&limit=10 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

### List Performance by Platform

**URL:** `/analytics/platforms`

**Method:** `GET`

Returns 1 entry per platform with analytics, ordered by engagement. Totals use the latest snapshot of each post published in the window.

| Field    | Type     | Required | Description                                                                            |
| -------- | -------- | -------- | -------------------------------------------------------------------------------------- |
| `since`  | `string` | No       | Include posts published on or after this ISO 8601 timestamp. Defaults to 24 hours ago. |
| `until`  | `string` | No       | Include posts published on or before this ISO 8601 timestamp. Defaults to now.         |
| `bucket` | `string` | No       | Time bucket size: `hour` or `day`. Defaults to `hour`.                                 |

Each item contains `platform`, `postsCount`, `engagementsCount`, `likesCount`, `commentsCount`, `viewsCount`, and `buckets`. Counts are strings except `postsCount`. `viewsCount` is null when the platform reports neither views nor impressions. Engagements are the sum of likes, comments or replies, shares, and saves.

***

### Get Analytics Summary

**URL:** `/analytics/summary`

**Method:** `GET`

Returns published and failed post counts for a time window, both in total and grouped by platform and time bucket. It also returns metric totals from each published post's latest snapshot.

| Field    | Type     | Required | Description                                                                                     |
| -------- | -------- | -------- | ----------------------------------------------------------------------------------------------- |
| `since`  | `string` | No       | Include posts published or failed on or after this ISO 8601 timestamp. Defaults to 30 days ago. |
| `until`  | `string` | No       | Include posts published or failed on or before this ISO 8601 timestamp. Defaults to now.        |
| `bucket` | `string` | No       | Time bucket size: `hour` or `day`. Defaults to `hour`.                                          |

The response contains `publishedCount`, `failedCount`, `platforms`, `buckets`, and `metrics`. `metrics` contains `postsCount`, `viewsCount`, `likesCount`, and `commentsCount`, or is null when analytics is unavailable.

***

### Get Post Analytics

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/posts/{id}/analytics`

**Method:** `GET`

#### Description

Returns the latest metrics and full snapshot history for a single published post.

The `id` is the published post id, which you get from the `items[].id` field of [List Top Performing Posts](#api-analytics--list-top-performing-posts) or [List Posts](#api-api-reference-list-posts) (published items). It is not the `postSubmissionId` returned when you publish.

If your request returns only `postSubmissionId`, `status`, and `publicUrl` with no metrics fields, you called [Get Post Status](#api-api-reference-get-post) instead of this analytics endpoint. Get Post checks publish status with the `postSubmissionId`. Analytics uses the published post id from List Posts or List Top Performing Posts.

#### Path Parameters

| Field | Type     | Required | Description                                                                                                                                                                                                                                                                                                         |
| ----- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`  | `string` | Yes      | The published post ID from `items[].id` in [List Top Performing Posts](#api-analytics--list-top-performing-posts) or a published item in [List Posts](#api-api-reference-list-posts). Do not substitute a `postSubmissionId`. The analytics path ends in `/analytics`, whereas the publishing-status path does not. |

#### Response

**Status Code:** `200 OK`

```json
{
  "publishedPostId": "12345",
  "platform": "instagram",
  "lastFetchedAt": "2026-06-11T12:00:00.000Z",
  "lastError": null,
  "metrics": {
    "viewsCount": "18400",
    "likesCount": "920",
    "commentsCount": "47",
    "reachCount": "56200"
  },
  "history": [
    {
      "fetchedAt": "2026-06-10T12:00:00.000Z",
      "metrics": { "viewsCount": "12100", "likesCount": "640" }
    },
    {
      "fetchedAt": "2026-06-11T12:00:00.000Z",
      "metrics": { "viewsCount": "18400", "likesCount": "920" }
    }
  ]
}
```

**Response Keys**

| Field             | Type             | Description                                                                                                                                                                                              |
| ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `publishedPostId` | `string`         | The id of the published post.                                                                                                                                                                            |
| `platform`        | `string`         | Social media platform.                                                                                                                                                                                   |
| `lastFetchedAt`   | `string \| null` | ISO 8601 timestamp of the last snapshot stored successfully. A failed fetch leaves this unchanged, so it reflects the last success, not the last attempt. Check `lastError` for the most recent failure. |
| `lastError`       | `string \| null` | The last error Blotato hit while fetching metrics for this post, if any.                                                                                                                                 |
| `metrics`         | `object \| null` | Latest metrics snapshot. See [Metrics](#api-analytics--metrics) and [Empty vs null metrics](#api-analytics--empty-vs-null-metrics).                                                                      |
| `history`         | `array`          | All collected snapshots, oldest first. Each is `{ fetchedAt, metrics }`.                                                                                                                                 |

#### Errors

| Status | Reason                                                                                                         |
| ------ | -------------------------------------------------------------------------------------------------------------- |
| `404`  | No published post with that `id`, it does not belong to your account, or your plan does not include analytics. |

#### Example

```http
GET https://backend.blotato.com/v2/posts/12345/analytics HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

### Metrics

Every metric field is optional and only appears when the platform reports it for that post. Count metrics are returned as strings to preserve large integers (e.g. `"18400"`). Rate and percentage metrics are returned as numbers, and breakdown metrics (such as `facebookPostReactionsByType`) return an object mapping each bucket to a count, e.g. `{ "like": "13", "haha": "2" }`.

A metric value of `0` is real and is always returned. It means zero, not missing.

#### Empty vs null metrics

An empty `metrics` object (`{}`) is not the same as `metrics: null`.

* `metrics: null` -- no analytics snapshot has been stored yet.
* `metrics: {}` -- a snapshot exists, but no metric values are being returned. Two causes: every value came back empty from the platform, or your plan returns a reduced metric set.

Common metrics like `viewsCount`, `likesCount`, and `reachCount` apply across platforms. Each platform also returns its own metrics, prefixed with the platform name — for example `twitterRetweetsCount` or `blueskyRepostsCount`.

See the [Metrics Reference](#api-analytics-metrics) for the full list of common and platform-specific metrics.

Platform-specific coverage depends on your plan. The Starter plan returns a limited set of these metrics. The Creator and Agency plans return every platform-specific metric and breakdown. See the [Metrics Reference](#api-analytics-metrics) for the field-level breakdown.

Inspect the `metrics` object in the response for the set available for your post.

***

### Related

* [List Posts](#api-api-reference-list-posts) — list scheduled, published, and failed posts; published items carry the `id` used by Get Post Analytics.
* [List Published Posts](#api-api-reference-list-published-posts) — full-text search over your published posts with the latest analytics snapshot attached.

***

## Analytics Metrics Reference

This page lists every metric field that can appear in an analytics snapshot returned by [List Top Performing Posts](#api-analytics--list-top-performing-posts) and [Get Post Analytics](#api-analytics--get-post-analytics).

Metrics live inside a snapshot's `metrics` object:

```json
{
  "fetchedAt": "2026-06-11T12:00:00.000Z",
  "metrics": {
    "viewsCount": "18400",
    "likesCount": "920",
    "twitterRetweetsCount": "45"
  }
}
```

#### How to read this reference

* **Every field is optional.** A metric only appears when the platform reports it for that specific post and when it's covered under your Blotato plan, so most snapshots contain a small subset of the fields below.
* **Counts are strings.** Count metrics are returned as strings (e.g. `"18400"`) to preserve large integers without precision loss.
* **Rates and percentages are numbers.** Ratios (`0`–`1`) and percentages (`0`–`100`) are returned as JSON numbers.
* **Breakdown metrics are objects.** A breakdown maps each bucket to a count string, e.g. `{ "like": "13", "haha": "2" }`. On the Published page these render as a bar chart with one bar per bucket.
* **Per-media metrics are arrays.** A few Twitter video metrics are arrays indexed by media position in the post (upload order across the whole thread). Entries are `null` for media items that are not videos.

Analytics are currently collected for **Twitter/X, Instagram, Facebook, Threads, Bluesky, TikTok, YouTube, and Pinterest**. Instagram and TikTok report only the [common metrics](#api-analytics-metrics--common-metrics) and have no platform-specific fields. The LinkedIn fields below are part of the API schema but are not populated yet.

***

### Common metrics

Reported across platforms whenever the platform exposes them.

| Field                  | Type     | Description                                        | Starter | Creator | Agency |
| ---------------------- | -------- | -------------------------------------------------- | :-----: | :-----: | :----: |
| `viewsCount`           | `string` | Views.                                             |    ✓    |    ✓    |    ✓   |
| `impressionsCount`     | `string` | Impressions.                                       |    ✓    |    ✓    |    ✓   |
| `reachCount`           | `string` | Unique accounts reached.                           |    ✓    |    ✓    |    ✓   |
| `likesCount`           | `string` | Likes.                                             |    ✓    |    ✓    |    ✓   |
| `commentsCount`        | `string` | Comments.                                          |    ✓    |    ✓    |    ✓   |
| `repliesCount`         | `string` | Replies.                                           |    ✓    |    ✓    |    ✓   |
| `sharesCount`          | `string` | Shares.                                            |    ✓    |    ✓    |    ✓   |
| `savesCount`           | `string` | Saves / bookmarks.                                 |    ✓    |    ✓    |    ✓   |
| `clicksCount`          | `string` | Link or content clicks.                            |    ✓    |    ✓    |    ✓   |
| `followsCount`         | `string` | Follows gained from the post.                      |    ✓    |    ✓    |    ✓   |
| `playsCount`           | `string` | Video / audio plays.                               |    ✓    |    ✓    |    ✓   |
| `profileVisitsCount`   | `string` | Profile visits driven by the post.                 |    ✓    |    ✓    |    ✓   |
| `profileActivityCount` | `string` | Profile actions driven by the post.                |    ✓    |    ✓    |    ✓   |
| `navigationsCount`     | `string` | In-post navigation actions (e.g. carousel swipes). |    ✓    |    ✓    |    ✓   |
| `interactionsSum`      | `string` | Total interactions.                                |    ✓    |    ✓    |    ✓   |
| `viewTimeMsSum`        | `string` | Total watch time, milliseconds.                    |    ✓    |    ✓    |    ✓   |
| `watchTimeMsAvg`       | `string` | Average watch time, milliseconds.                  |    ✓    |    ✓    |    ✓   |

***

### Twitter / X

Post-level metrics.

| Field                  | Type     | Description   | Starter | Creator | Agency |
| ---------------------- | -------- | ------------- | :-----: | :-----: | :----: |
| `twitterRetweetsCount` | `string` | Retweets.     |    ✓    |    ✓    |    ✓   |
| `twitterQuotesCount`   | `string` | Quote tweets. |    ✓    |    ✓    |    ✓   |

Per-media video metrics. Each is an array indexed by media position in the post; `null` entries mark non-video media.

| Field                                       | Type                 | Description                            | Starter | Creator | Agency |
| ------------------------------------------- | -------------------- | -------------------------------------- | :-----: | :-----: | :----: |
| `twitterPerMediaVideoViewsCount`            | `(string \| null)[]` | Video views per media item.            |         |    ✓    |    ✓   |
| `twitterPerMediaVideoPlaybackStartCount`    | `(string \| null)[]` | Playbacks started per media item.      |         |    ✓    |    ✓   |
| `twitterPerMediaVideoPlayback25Count`       | `(string \| null)[]` | Playbacks reaching 25% per media item. |         |    ✓    |    ✓   |
| `twitterPerMediaVideoPlayback50Count`       | `(string \| null)[]` | Playbacks reaching 50% per media item. |         |    ✓    |    ✓   |
| `twitterPerMediaVideoPlayback75Count`       | `(string \| null)[]` | Playbacks reaching 75% per media item. |         |    ✓    |    ✓   |
| `twitterPerMediaVideoPlaybackCompleteCount` | `(string \| null)[]` | Playbacks completed per media item.    |         |    ✓    |    ✓   |

***

### Bluesky

Post-level metrics.

| Field                 | Type     | Description  | Starter | Creator | Agency |
| --------------------- | -------- | ------------ | :-----: | :-----: | :----: |
| `blueskyRepostsCount` | `string` | Reposts.     |    ✓    |    ✓    |    ✓   |
| `blueskyQuotesCount`  | `string` | Quote posts. |    ✓    |    ✓    |    ✓   |

***

### Threads

Post-level metrics.

| Field                 | Type     | Description  | Starter | Creator | Agency |
| --------------------- | -------- | ------------ | :-----: | :-----: | :----: |
| `threadsRepostsCount` | `string` | Reposts.     |    ✓    |    ✓    |    ✓   |
| `threadsQuotesCount`  | `string` | Quote posts. |    ✓    |    ✓    |    ✓   |

***

### Facebook

Post-level metrics.

| Field                               | Type     | Description                                                  | Starter | Creator | Agency |
| ----------------------------------- | -------- | ------------------------------------------------------------ | :-----: | :-----: | :----: |
| `facebookPostMediaViewsCount`       | `string` | Media views on the post.                                     |         |    ✓    |    ✓   |
| `facebookPostMediaViewsUniqueCount` | `string` | Unique media views on the post.                              |         |    ✓    |    ✓   |
| `facebookPostReactionsByType`       | `object` | Reactions broken down by type (e.g. `like`, `love`, `haha`). |         |    ✓    |    ✓   |

Video and Reels metrics.

| Field                                               | Type     | Description                                                      | Starter | Creator | Agency |
| --------------------------------------------------- | -------- | ---------------------------------------------------------------- | :-----: | :-----: | :----: |
| `facebookBlueReelsPlaysCount`                       | `string` | Reel plays.                                                      |         |    ✓    |    ✓   |
| `facebookFbReelsReplaysCount`                       | `string` | Reel replays.                                                    |         |    ✓    |    ✓   |
| `facebookFbReelsTotalPlaysCount`                    | `string` | Total reel plays (plays plus replays).                           |         |    ✓    |    ✓   |
| `facebookPostVideoWatchTimeMsAvg`                   | `string` | Average video watch time, milliseconds.                          |         |    ✓    |    ✓   |
| `facebookPostVideoFollowersCount`                   | `string` | Followers gained from the video.                                 |         |    ✓    |    ✓   |
| `facebookPostVideoLikesByReactionType`              | `object` | Video likes broken down by reaction type.                        |         |    ✓    |    ✓   |
| `facebookPostVideoSocialActionsByType`              | `object` | Video social actions broken down by type.                        |         |    ✓    |    ✓   |
| `facebookPostVideoViewTimeMsSum`                    | `string` | Total video view time, milliseconds.                             |         |    ✓    |    ✓   |
| `facebookTotalVideoViewsCount`                      | `string` | Total video views.                                               |         |    ✓    |    ✓   |
| `facebookTotalVideoViewsUniqueCount`                | `string` | Unique video views.                                              |         |    ✓    |    ✓   |
| `facebookTotalVideoViewsAutoplayedCount`            | `string` | Autoplayed video views.                                          |         |    ✓    |    ✓   |
| `facebookTotalVideoViewsClickedToPlayCount`         | `string` | Click-to-play video views.                                       |         |    ✓    |    ✓   |
| `facebookTotalVideoViewsSoundOnCount`               | `string` | Video views with sound on.                                       |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsCount`              | `string` | Complete video views.                                            |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsUniqueCount`        | `string` | Unique complete video views.                                     |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsAutoplayedCount`    | `string` | Autoplayed complete video views.                                 |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsClickedToPlayCount` | `string` | Click-to-play complete video views.                              |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsOrganicCount`       | `string` | Organic complete video views.                                    |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsOrganicUniqueCount` | `string` | Unique organic complete video views.                             |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsPaidCount`          | `string` | Paid complete video views.                                       |         |    ✓    |    ✓   |
| `facebookTotalVideoCompleteViewsPaidUniqueCount`    | `string` | Unique paid complete video views.                                |         |    ✓    |    ✓   |
| `facebookTotalVideoViews10sCount`                   | `string` | Video views of at least 10 seconds.                              |         |    ✓    |    ✓   |
| `facebookTotalVideoViews10sUniqueCount`             | `string` | Unique 10-second video views.                                    |         |    ✓    |    ✓   |
| `facebookTotalVideoViews10sAutoplayedCount`         | `string` | Autoplayed 10-second video views.                                |         |    ✓    |    ✓   |
| `facebookTotalVideoViews10sClickedToPlayCount`      | `string` | Click-to-play 10-second video views.                             |         |    ✓    |    ✓   |
| `facebookTotalVideoViews10sOrganicCount`            | `string` | Organic 10-second video views.                                   |         |    ✓    |    ✓   |
| `facebookTotalVideoViews10sPaidCount`               | `string` | Paid 10-second video views.                                      |         |    ✓    |    ✓   |
| `facebookTotalVideoViews10sSoundOnCount`            | `string` | 10-second video views with sound on.                             |         |    ✓    |    ✓   |
| `facebookTotalVideoViews15sCount`                   | `string` | Video views of at least 15 seconds.                              |         |    ✓    |    ✓   |
| `facebookTotalVideoViews60sExcludesShorterCount`    | `string` | Video views of at least 60 seconds (excludes shorter).           |         |    ✓    |    ✓   |
| `facebookTotalVideoWatchTimeMsAvg`                  | `string` | Average total video watch time, milliseconds.                    |         |    ✓    |    ✓   |
| `facebookTotalVideoViewTimeMsSum`                   | `string` | Total video view time, milliseconds.                             |         |    ✓    |    ✓   |
| `facebookTotalVideoViewTimeOrganicMsSum`            | `string` | Organic video view time, milliseconds.                           |         |    ✓    |    ✓   |
| `facebookTotalVideoViewTimePaidMsSum`               | `string` | Paid video view time, milliseconds.                              |         |    ✓    |    ✓   |
| `facebookTotalVideoStoriesByActionType`             | `object` | Video stories (shares/comments/etc.) broken down by action type. |         |    ✓    |    ✓   |
| `facebookTotalVideoReactionsByType`                 | `object` | Video reactions broken down by type.                             |         |    ✓    |    ✓   |
| `facebookTotalVideoViewTimeMsByAgeBucketAndGender`  | `object` | Video view time broken down by age bucket and gender.            |         |    ✓    |    ✓   |
| `facebookTotalVideoViewTimeMsByRegionId`            | `object` | Video view time broken down by region.                           |         |    ✓    |    ✓   |
| `facebookTotalVideoViewsByDistributionType`         | `object` | Video views broken down by distribution type.                    |         |    ✓    |    ✓   |

***

### LinkedIn

Post-level metrics.

| Field                             | Type     | Description                                                       | Starter | Creator | Agency |
| --------------------------------- | -------- | ----------------------------------------------------------------- | :-----: | :-----: | :----: |
| `linkedinFirstLevelCommentsCount` | `string` | First-level comments (excludes replies).                          |    ✓    |    ✓    |    ✓   |
| `linkedinReactionsByType`         | `object` | Reactions broken down by type (e.g. `like`, `praise`, `empathy`). |         |    ✓    |    ✓   |

***

### Pinterest

Post-level metrics.

| Field                             | Type     | Description                                    | Starter | Creator | Agency |
| --------------------------------- | -------- | ---------------------------------------------- | :-----: | :-----: | :----: |
| `pinterestPinClicksCount`         | `string` | Pin clicks.                                    |         |    ✓    |    ✓   |
| `pinterestReactionsCount`         | `string` | Reactions.                                     |    ✓    |    ✓    |    ✓   |
| `pinterestVideoP95ViewsCount`     | `string` | Video views reaching 95% of the video.         |         |    ✓    |    ✓   |
| `pinterestVideo10sViewsCount`     | `string` | Video views of at least 10 seconds.            |         |    ✓    |    ✓   |
| `pinterestVideoMrcViewsCount`     | `string` | MRC-standard video views.                      |         |    ✓    |    ✓   |
| `pinterestVideoV50WatchTimeMsSum` | `string` | Watch time for views past 50%, milliseconds.   |         |    ✓    |    ✓   |
| `pinterestSaveRate`               | `number` | Saves divided by impressions, a `0`–`1` ratio. |         |    ✓    |    ✓   |

***

### YouTube

YouTube metrics come from the YouTube Analytics API, which reports only finalized views after YouTube filters spam and invalid traffic. `viewsCount` for a YouTube post therefore reads lower than the count on the YouTube watch page, and the gap narrows as YouTube finalizes more data. See: [Analytics](#api-analytics--youtube-view-counts-trail-the-public-count).

Post-level metrics.

| Field                                        | Type     | Description                                                 | Starter | Creator | Agency |
| -------------------------------------------- | -------- | ----------------------------------------------------------- | :-----: | :-----: | :----: |
| `youtubeDislikesCount`                       | `string` | Dislikes.                                                   |         |    ✓    |    ✓   |
| `youtubeSubscribersLostCount`                | `string` | Subscribers lost from the video.                            |         |    ✓    |    ✓   |
| `youtubeVideosAddedToPlaylistsCount`         | `string` | Times the video was added to a playlist.                    |         |    ✓    |    ✓   |
| `youtubeVideosRemovedFromPlaylistsCount`     | `string` | Times the video was removed from a playlist.                |         |    ✓    |    ✓   |
| `youtubeRedViewsCount`                       | `string` | YouTube Premium (Red) views.                                |         |    ✓    |    ✓   |
| `youtubeRedViewTimeMsSum`                    | `string` | YouTube Premium (Red) watch time, milliseconds.             |         |    ✓    |    ✓   |
| `youtubeEngagedViewsCount`                   | `string` | Engaged views.                                              |         |    ✓    |    ✓   |
| `youtubeAverageViewPercentage`               | `number` | Average percentage of the video watched, `0`–`100`.         |         |    ✓    |    ✓   |
| `youtubeCardClicksCount`                     | `string` | Card clicks.                                                |         |    ✓    |    ✓   |
| `youtubeCardImpressionsCount`                | `string` | Card impressions.                                           |         |    ✓    |    ✓   |
| `youtubeCardClickRate`                       | `number` | Card clicks divided by impressions, a `0`–`1` ratio.        |         |    ✓    |    ✓   |
| `youtubeCardTeaserClicksCount`               | `string` | Card teaser clicks.                                         |         |    ✓    |    ✓   |
| `youtubeCardTeaserImpressionsCount`          | `string` | Card teaser impressions.                                    |         |    ✓    |    ✓   |
| `youtubeCardTeaserClickRate`                 | `number` | Card teaser clicks divided by impressions, a `0`–`1` ratio. |         |    ✓    |    ✓   |
| `youtubeAnnotationClicksCount`               | `string` | Annotation clicks.                                          |         |    ✓    |    ✓   |
| `youtubeAnnotationClickableImpressionsCount` | `string` | Clickable annotation impressions.                           |         |    ✓    |    ✓   |
| `youtubeAnnotationClosesCount`               | `string` | Annotation closes.                                          |         |    ✓    |    ✓   |
| `youtubeAnnotationClosableImpressionsCount`  | `string` | Closable annotation impressions.                            |         |    ✓    |    ✓   |
| `youtubeAnnotationImpressionsCount`          | `string` | Annotation impressions.                                     |         |    ✓    |    ✓   |
| `youtubeAnnotationClickThroughRate`          | `number` | Annotation click-through rate, a `0`–`1` ratio.             |         |    ✓    |    ✓   |
| `youtubeAnnotationCloseRate`                 | `number` | Annotation close rate, a `0`–`1` ratio.                     |         |    ✓    |    ✓   |

***

### Related

* [Analytics](#api-analytics) — the two endpoints that return these metrics.
* [List Top Performing Posts](#api-analytics--list-top-performing-posts) — `GET /v2/analytics`
* [Get Post Analytics](#api-analytics--get-post-analytics) — `GET /v2/posts/{id}/analytics`

***

## Comments /v2/comments

Blotato allows you to read and post comments on your published Instagram posts and Facebook Page posts. Twitter/X, TikTok, LinkedIn, Pinterest, Threads, Bluesky, and YouTube are not supported at this time. Three endpoints let you list, read, and post comments:

* [List Comments](#api-api-reference-comments--list-comments) — `GET /v2/comments`
* [Get Comment](#api-api-reference-comments--get-comment) — `GET /v2/comments/:commentId`
* [Post Comment](#api-api-reference-comments--post-comment) — `POST /v2/comments`

Comments are available on all paid plans.

To manage Instagram and Facebook comments in the Blotato web app, see [Comments and Messaging Inbox](/web-app-features/inbox.md).

Blotato does not backfill comments. It starts capturing comments once your account has comments enabled and you connect or reconnect your account. Comments left before then do not appear in these endpoints.

Blotato retains comments for up to 45 days. Comments older than 45 days are not available through these endpoints.

The `/comments` endpoints cover two directions:

* **Inbound comments** your audience leaves on your posts. These have `isAuthor: false`.
* **Outbound comments** you post through Blotato. These have `isAuthor: true`.

The comments API lists and posts top-level comments and one level of replies. You can reply to a top-level comment on your post, but you cannot reply to a reply.

### Before You Start

Blotato must have permissions to read and post comments on your account before you can use the Blotato comments endpoints.

If you connected your account before comments launched, reconnect it so Blotato has the new permission.

* For Instagram, see [Connect Instagram](/settings/social-accounts/instagram.md).
* For Facebook, see [Connect Facebook](/settings/social-accounts/facebook.md).

***

### List Comments

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/comments`

**Method:** `GET`

#### Description

Returns your comments across connected accounts, ordered by creation time (most recent first). Use cursor-based pagination and the optional filters below.

#### Query Parameters

| Field             | Type      | Required | Description                                                         |
| ----------------- | --------- | -------- | ------------------------------------------------------------------- |
| `limit`           | `integer` | No       | Maximum comments to return (1-250). Default 50.                     |
| `cursor`          | `string`  | No       | Cursor from a previous response. Pass it to fetch the next page.    |
| `platform`        | `array`   | No       | Filter by platform. Values: `instagram`, `facebook`.                |
| `accountId`       | `string`  | No       | Filter to a single connected account.                               |
| `parentCommentId` | `string`  | No       | Filter to direct replies to a single comment.                       |
| `postId`          | `string`  | No       | Filter to comments on a single post using its Blotato post ID.      |
| `since`           | `string`  | No       | Only include comments created on or after this ISO 8601 timestamp.  |
| `until`           | `string`  | No       | Only include comments created on or before this ISO 8601 timestamp. |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "cmt_abc123",
      "accountId": "98434",
      "platform": "instagram",
      "postId": "post_xyz789",
      "platformPostId": "17851234567890",
      "platformCommentId": "17899876543210",
      "authorId": "1784000000",
      "isAuthor": false,
      "text": "Love this!",
      "status": "posted",
      "errorCode": null,
      "errorMessage": null,
      "createdAt": "2026-07-01T12:34:56Z"
    }
  ],
  "cursor": "eyJz..."
}
```

| Field    | Type     | Description                                                                          |
| -------- | -------- | ------------------------------------------------------------------------------------ |
| `items`  | `array`  | List of comments. See [Comment Object](#api-api-reference-comments--comment-object). |
| `cursor` | `string` | Cursor for the next page. Absent when there are no more comments.                    |

***

### Get Comment

#### Endpoint

**URL:** `/comments/:commentId`

**Method:** `GET`

#### Description

Fetches a single comment by its Blotato ID. The comment is an inbound reply from your audience or an outbound comment you posted through Blotato.

#### Path Parameters

| Field       | Type     | Required | Description                |
| ----------- | -------- | -------- | -------------------------- |
| `commentId` | `string` | Yes      | Blotato ID of the comment. |

#### Response

**Status Code:** `200 OK`

Returns a [Comment Object](#api-api-reference-comments--comment-object).

***

### Post Comment

#### Endpoint

**URL:** `/comments`

**Method:** `POST`

#### Description

Posts a comment on one of your published posts, or a reply to an existing top-level comment when you pass `parentCommentId`. Blotato queues the comment and posts it in the background, so the response returns status `queued`. Poll [Get Comment](#api-api-reference-comments--get-comment) with the returned `id` to confirm the comment reached `posted` or `failed`.

#### Request Body

```json
{
  "postId": "post_xyz789",
  "text": "Thanks everyone!"
}
```

| Field             | Type     | Required | Description                                                                                                                                                                                                                                                         |
| ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `postId`          | `string` | Yes      | Blotato ID of the published post to comment on. For a reply, use the parent comment's non-null `postId`. Otherwise, use [List Posts](#api-api-reference-list-posts), from items whose `state.type` is `published`. Do not substitute the social platform's post ID. |
| `text`            | `string` | Yes      | Plain-text comment body. Instagram allows up to 2200 characters, Facebook up to 8000.                                                                                                                                                                               |
| `parentCommentId` | `string` | No       | Blotato ID of a top-level, posted comment on the same post to reply to. Replies to replies are not supported.                                                                                                                                                       |

#### Response

**Status Code:** `201 Created`

Returns a [Comment Object](#api-api-reference-comments--comment-object) with status `queued`.

#### Errors

| Status | Reason                                                                                                                                                                                                                                                                         |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `404`  | The published post or parent comment was not found, or comments are not enabled for your account.                                                                                                                                                                              |
| `422`  | The post's platform does not support comments, the comment exceeds the platform's character limit, the parent comment is invalid, Blotato lacks the Instagram comment permission (error code `20203`), or you reached your monthly active-contacts limit (error code `20101`). |
| `429`  | Rate limit exceeded (30 requests per minute).                                                                                                                                                                                                                                  |

#### Replies and Your Contacts Limit

Replying to a comment from your audience counts the person as an active contact for the month. Your current account limit controls how many active contacts you reach. When you pass the limit, Post Comment returns `422` with error code `20101`. Replying to your own comment does not count.

See [Active Contacts](/settings/billing-and-credits.md#active-contacts) for the definition, standard plan limits, and how to check your current limit.

***

### Comment Object

| Field               | Type              | Description                                                                                                                                                                       |
| ------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                | `string`          | Blotato comment ID.                                                                                                                                                               |
| `accountId`         | `string`          | ID of the connected account the comment belongs to.                                                                                                                               |
| `platform`          | `string`          | `instagram` or `facebook`.                                                                                                                                                        |
| `postId`            | `string or null`  | Blotato ID of the tracked post. An incoming Instagram comment also creates this ID for posts published outside Blotato. A populated ID does not prove Blotato published the post. |
| `parentCommentId`   | `string or null`  | Blotato ID of the parent comment when this comment is a reply. `null` for top-level comments.                                                                                     |
| `platformPostId`    | `string`          | Social platform (e.g. Instagram) ID of the parent post.                                                                                                                           |
| `platformCommentId` | `string or null`  | Social platform (e.g. Instagram) ID of the comment.                                                                                                                               |
| `authorId`          | `string or null`  | Social platform (e.g. Instagram) ID of the comment author.                                                                                                                        |
| `isAuthor`          | `boolean`         | `true` when your connected account authored the comment. `false` for an audience reply.                                                                                           |
| `text`              | `string`          | Comment text.                                                                                                                                                                     |
| `status`            | `string`          | Comment status. See [Status Values](#api-api-reference-comments--status-values).                                                                                                  |
| `errorCode`         | `integer or null` | Set only when status is `failed`. See [Comments Errors](/support/errors.md#comments-errors).                                                                                      |
| `errorMessage`      | `string or null`  | Set only when status is `failed`.                                                                                                                                                 |
| `createdAt`         | `string`          | ISO 8601 timestamp when the comment was created.                                                                                                                                  |

#### Status Values

| Status       | Meaning                           |
| ------------ | --------------------------------- |
| `queued`     | Accepted and waiting to post.     |
| `processing` | Posting to the social platform.   |
| `posted`     | Live on the social platform.      |
| `failed`     | Did not post. See `errorMessage`. |
| `deleted`    | Removed from the social platform. |

#### Rate Limits

| Endpoint                   | Limit    |
| -------------------------- | -------- |
| `GET /comments`            | 60 / min |
| `GET /comments/:commentId` | 60 / min |
| `POST /comments`           | 30 / min |

***

## Messages /v2/messages

Blotato allows you to read and send direct messages on your Instagram account and Facebook Page. Twitter/X, TikTok, LinkedIn, Pinterest, Threads, Bluesky, and YouTube are not supported at this time. Five endpoints let you list conversations, read a conversation, list messages, read a message, and send a message:

* [List Conversations](#api-api-reference-messages--list-conversations) — `GET /v2/conversations`
* [Get Conversation](#api-api-reference-messages--get-conversation) — `GET /v2/conversations/:conversationId`
* [List Messages](#api-api-reference-messages--list-messages) — `GET /v2/messages`
* [Get Message](#api-api-reference-messages--get-message) — `GET /v2/messages/:messageId`
* [Send Message](#api-api-reference-messages--send-message) — `POST /v2/messages`

Messages are available on all paid plans.

To manage Instagram and Facebook conversations in the Blotato web app, see [Comments and Messaging Inbox](/web-app-features/inbox.md).

To send automatic replies after a comment or incoming message, use [DM Automations](/support/dm-automations.md). You manage these automations in the web app or through an AI tool connected to Blotato MCP.

Each message has a `direction`:

* **Incoming messages** your contacts send you. These have `direction: "incoming"`.
* **Outgoing messages** you send through Blotato. These have `direction: "outgoing"`.

You reply to people who already have a conversation with you. You send to a `recipientId` taken from an existing conversation or an incoming message, not to an arbitrary user. The social platform limits who you can message and when. See [Messaging Windows](#api-api-reference-messages--messaging-windows).

Blotato retains messages for up to 45 days. Messages older than 45 days are not available through these endpoints.

### Before You Start

Blotato must have permissions to read and send messages on your account before you can use the Blotato messaging endpoints.

If you connected your account before messaging launched, reconnect it so Blotato has the new permission.

* For Instagram, see [Connect Instagram](/settings/social-accounts/instagram.md).
* For Facebook, see [Connect Facebook](/settings/social-accounts/facebook.md).

***

### List Conversations

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/conversations`

**Method:** `GET`

#### Description

Returns your conversations across connected accounts, ordered by most recent activity. Use cursor-based pagination and the optional filters below.

#### Query Parameters

| Field       | Type      | Required | Description                                                      |
| ----------- | --------- | -------- | ---------------------------------------------------------------- |
| `limit`     | `integer` | No       | Maximum conversations to return (1-250). Default 50.             |
| `cursor`    | `string`  | No       | Cursor from a previous response. Pass it to fetch the next page. |
| `platform`  | `string`  | No       | Filter by platform. Values: `instagram`, `facebook`.             |
| `accountId` | `string`  | No       | Filter to a single connected account.                            |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "cnv_abc123",
      "accountId": "98434",
      "platform": "instagram",
      "participants": [
        {
          "id": "1784000000",
          "name": "Jamie Rivera",
          "username": "jamie.rivera",
          "profileImageUrl": "https://example.com/avatar.jpg"
        }
      ],
      "createdAt": "2026-07-01T12:00:00Z",
      "updatedAt": "2026-07-02T09:30:00Z"
    }
  ],
  "cursor": "eyJz..."
}
```

| Field    | Type     | Description                                                                                         |
| -------- | -------- | --------------------------------------------------------------------------------------------------- |
| `items`  | `array`  | List of conversations. See [Conversation Object](#api-api-reference-messages--conversation-object). |
| `cursor` | `string` | Cursor for the next page. Absent when there are no more conversations.                              |

***

### Get Conversation

#### Endpoint

**URL:** `/conversations/:conversationId`

**Method:** `GET`

#### Description

Fetches a single conversation by its Blotato ID.

#### Path Parameters

| Field            | Type     | Required | Description                     |
| ---------------- | -------- | -------- | ------------------------------- |
| `conversationId` | `string` | Yes      | Blotato ID of the conversation. |

#### Response

**Status Code:** `200 OK`

Returns a [Conversation Object](#api-api-reference-messages--conversation-object).

***

### List Messages

#### Endpoint

**URL:** `/messages`

**Method:** `GET`

#### Description

Returns your messages across connected accounts, ordered by creation time (most recent first). Use cursor-based pagination and the optional filters below.

#### Query Parameters

| Field            | Type      | Required | Description                                                      |
| ---------------- | --------- | -------- | ---------------------------------------------------------------- |
| `limit`          | `integer` | No       | Maximum messages to return (1-250). Default 50.                  |
| `cursor`         | `string`  | No       | Cursor from a previous response. Pass it to fetch the next page. |
| `conversationId` | `string`  | No       | Filter to messages in a single conversation.                     |
| `platform`       | `array`   | No       | Filter by platform. Values: `instagram`, `facebook`.             |
| `accountId`      | `string`  | No       | Filter to a single connected account.                            |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "msg_abc123",
      "conversationId": "cnv_abc123",
      "platform": "instagram",
      "direction": "incoming",
      "senderId": "1784000000",
      "recipientId": "17841400000000000",
      "text": "Hi! Is this still available?",
      "status": "delivered",
      "errorCode": null,
      "errorMessage": null,
      "createdAt": "2026-07-02T09:30:00Z"
    }
  ],
  "cursor": "eyJz..."
}
```

| Field    | Type     | Description                                                                          |
| -------- | -------- | ------------------------------------------------------------------------------------ |
| `items`  | `array`  | List of messages. See [Message Object](#api-api-reference-messages--message-object). |
| `cursor` | `string` | Cursor for the next page. Absent when there are no more messages.                    |

***

### Get Message

#### Endpoint

**URL:** `/messages/:messageId`

**Method:** `GET`

#### Description

Fetches a single message by its Blotato ID. Poll this after [Send Message](#api-api-reference-messages--send-message) to check whether an outgoing message reached `sent` or `failed`.

#### Path Parameters

| Field       | Type     | Required | Description                |
| ----------- | -------- | -------- | -------------------------- |
| `messageId` | `string` | Yes      | Blotato ID of the message. |

#### Response

**Status Code:** `200 OK`

Returns a [Message Object](#api-api-reference-messages--message-object).

***

### Send Message

#### Endpoint

**URL:** `/messages`

**Method:** `POST`

#### Description

Sends a direct message to one recipient. Blotato queues the message and sends it in the background, so the response returns status `queued`. Poll [Get Message](#api-api-reference-messages--get-message) with the returned `id` to confirm the message reached `sent` or `failed`.

Send to a `recipientId` taken from an existing conversation or an incoming message. The social platform limits who you can message and when. See [Messaging Windows](#api-api-reference-messages--messaging-windows).

#### Request Body

```json
{
  "accountId": "98434",
  "recipientId": "1784000000",
  "text": "Thanks for reaching out! Yes, it's available.",
  "target": {
    "targetType": "instagram"
  }
}
```

| Field         | Type     | Required | Description                                                                                                                                                                                                                                            |
| ------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `accountId`   | `string` | Yes      | Blotato ID of the connected account the message is sent from.                                                                                                                                                                                          |
| `recipientId` | `string` | Yes      | Social platform (e.g. Instagram) ID of the recipient. Get it from an existing conversation or incoming message.                                                                                                                                        |
| `text`        | `string` | Yes      | Plain-text message body. Instagram messages are limited to 1000 bytes, Facebook to 2000 characters. With buttons attached, the limit drops to 640 characters. See [Buttons and Quick Replies](#api-api-reference-messages--buttons-and-quick-replies). |
| `target`      | `object` | Yes      | Where and how to send. See [Target](#api-api-reference-messages--target).                                                                                                                                                                              |

#### Target

| Field          | Type     | Required     | Description                                                                                                                                                                                |
| -------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `targetType`   | `string` | Yes          | Value: `instagram` or `facebook`.                                                                                                                                                          |
| `pageId`       | `string` | For Facebook | ID of the Facebook Page to send from, from [subaccounts](#api-api-reference-accounts--list-subaccounts-pages). Required when `targetType` is `facebook`.                                   |
| `commentId`    | `string` | No           | Blotato comment ID. When set, Blotato delivers the message as a private reply to that comment instead of a direct message.                                                                 |
| `attachment`   | `object` | No           | Up to 3 call-to-action buttons pinned under the message. See [Buttons and Quick Replies](#api-api-reference-messages--buttons-and-quick-replies).                                          |
| `quickReplies` | `array`  | No           | Up to 13 tappable reply chips shown under the message. See [Buttons and Quick Replies](#api-api-reference-messages--buttons-and-quick-replies).                                            |
| `responseType` | `string` | No           | How the message fits the platform's messaging window: `RESPONSE`, `UPDATE`, or `MESSAGE_TAG`. Default `RESPONSE`. See [Messaging Windows](#api-api-reference-messages--messaging-windows). |
| `tag`          | `string` | No           | Required when `responseType` is `MESSAGE_TAG` (e.g. `HUMAN_AGENT`).                                                                                                                        |

#### Buttons and Quick Replies

Facebook and Instagram support buttons and quick replies in direct messages. Set `target.attachment` for buttons, or `target.quickReplies` for chips. The two are mutually exclusive: a request carrying both is rejected with `422`.

For any platform-applied limitations, see:

* [Instagram Limitations](/social-accounts-and-platform-faqs/instagram/limitations.md#direct-messages-and-dm-automations)
* [Facebook Limitations](/social-accounts-and-platform-faqs/facebook/limitations.md#direct-messages-and-dm-automations)

**Buttons**

Buttons sit under the message and stay there. Up to 3 buttons are supported.

**Instagram Limitation:** Instagram only renders buttons in the Instagram mobile app. A recipient reading the conversation on instagram.com in a desktop browser sees the message text with no buttons.

Send an `attachment` or a URL inside `text`, not both. A message carrying both leaves the URL in `text` unclickable on desktop, so a desktop recipient ends up with no working link. For a primarily desktop audience, drop the `attachment` and put the URL in `text`.

```json
{
  "accountId": "98434",
  "recipientId": "1784000000",
  "text": "Thanks for reaching out! Pick an option below.",
  "target": {
    "targetType": "instagram",
    "attachment": {
      "type": "button",
      "buttons": [
        { "type": "web_url", "title": "View pricing", "url": "https://example.com/pricing" },
        { "type": "postback", "title": "Talk to sales", "payload": "SALES" }
      ]
    }
  }
}
```

| Field     | Type     | Required | Description      |
| --------- | -------- | -------- | ---------------- |
| `type`    | `string` | Yes      | Value: `button`. |
| `buttons` | `array`  | Yes      | 1 to 3 buttons.  |

Each button has:

| Field     | Type     | Required       | Description                                                     |
| --------- | -------- | -------------- | --------------------------------------------------------------- |
| `type`    | `string` | Yes            | `web_url` opens a link. `postback` reports the tap back to you. |
| `title`   | `string` | Yes            | Button label, 1 to 20 characters.                               |
| `url`     | `string` | For `web_url`  | Link the button opens. Starts with `https://`.                  |
| `payload` | `string` | For `postback` | Value echoed back on tap, 1 to 1000 characters.                 |

With buttons set, `text` is required and limited to 640 characters.

**Quick Replies**

Quick replies are tappable chips offered as canned answers. They disappear once the person taps one or types their own message. Pass 1 to 13.

```json
{
  "accountId": "98434",
  "recipientId": "1784000000",
  "text": "What size do you need?",
  "target": {
    "targetType": "instagram",
    "quickReplies": [
      { "title": "Small", "payload": "SIZE_S" },
      { "title": "Medium", "payload": "SIZE_M" },
      { "title": "Large", "payload": "SIZE_L" }
    ]
  }
}
```

| Field     | Type     | Required | Description                                     |
| --------- | -------- | -------- | ----------------------------------------------- |
| `title`   | `string` | Yes      | Chip label, 1 to 20 characters.                 |
| `payload` | `string` | Yes      | Value echoed back on tap, 1 to 1000 characters. |

Quick replies require non-empty message `text`.

**Reading a Tap**

A quick-reply tap and a postback tap both arrive as the next **incoming** message carrying `payload.selection`, but they reach you differently.

Quick replies send a real text message. `text` holds the chip label, adn Blotato attaches `payload.selection` alongside it.

```json
{
  "id": "msg_def456",
  "direction": "incoming",
  "text": "Your chip label",
  "payload": {
    "selection": { "type": "quick-reply", "payload": "The chip payload" }
  }
}
```

Postbacks do not send a text message. Blotato records the tap as an incoming message, sets `text` to the button label, and attaches `payload.selection`.

```json
{
  "id": "msg_ghi789",
  "direction": "incoming",
  "text": "Your button label",
  "payload": {
    "selection": { "type": "postback", "payload": "SALES" }
  }
}
```

| Field               | Value            | Meaning                                                                                                                                                                                     |
| ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `selection.type`    | `quick-reply`    | The person tapped a chip. `text` is the chip label.                                                                                                                                         |
| `selection.type`    | `postback`       | The person tapped a button you attached. `text` is the button label.                                                                                                                        |
| `selection.payload` | `string or null` | The payload received with the tap. A postback is `null` when the platform webhook supplies no payload. Creating a postback button through this API requires a payload of 1–1000 characters. |

Branch on `selection.payload`, not on `text`. Two chips that share a label are distinguishable only by their payload.

A `web_url` button produces no selection. The link opens and nothing comes back.

Blotato subscribes your account to the postback webhook when you connect it. If you connected your account before buttons were launched, [reconnect](https://my.blotato.com/accounts) to start receiving postback events. Quick-reply taps need no reconnect.

#### Response

**Status Code:** `201 Created`

Returns a [Message Object](#api-api-reference-messages--message-object) with status `queued`.

#### Errors

Send Message returns `201` on success, `422` when the request is rejected, and `429` when you exceed 30 requests per minute.

Messaging is available on every plan, so there is no "not enabled" rejection, and this endpoint does not return `404`. A connected account that is missing or expired surfaces after the message is queued, as a `failed` message.

A `422` means one of:

* The message exceeds the platform's character limit. Instagram allows 1000 bytes, Facebook 2000 characters, and either drops to 640 characters when buttons are set.
* You passed both `attachment` and `quickReplies`. Send one or the other.
* You set buttons or quick replies without message `text`.
* The messaging window has closed.
* The comment you are replying to is invalid, or it already received a private reply.
* You reached your monthly active-contacts limit (error code 20101).
* `responseType` is `MESSAGE_TAG` and you did not set `tag`.

Message-send failures that happen after the message is queued do not return an error here. The message reaches status `failed` with an `errorCode` and `errorMessage`. See [Messaging Errors](/support/errors.md#messaging-errors).

***

### Messaging Windows

The social platform limits who you can message and when. You reply to people who already have a conversation with you, not arbitrary users.

* **Standard window:** send a `RESPONSE` within 24 hours of the contact's last message.
* **Private reply to a comment:** set `commentId` on the target to reply once, within 7 days of the comment.
* **Outside the window:** set `responseType` to `MESSAGE_TAG` with an approved `tag` (e.g. `HUMAN_AGENT`) to send a permitted follow-up.

These windows come from the social platform, not Blotato. A message sent outside an allowed window reaches status `failed`.

Sending a message to a new person also counts toward your monthly active-contacts limit. See [Active Contacts](/settings/billing-and-credits.md#active-contacts).

***

### Conversation Object

| Field          | Type     | Description                                                                                                                 |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id`           | `string` | Blotato conversation ID.                                                                                                    |
| `accountId`    | `string` | ID of the connected account this conversation belongs to.                                                                   |
| `platform`     | `string` | `instagram` or `facebook`.                                                                                                  |
| `participants` | `array`  | The other people in the conversation. Each has `id`, `name`, `username`, and `profileImageUrl`, any of which may be `null`. |
| `createdAt`    | `string` | ISO 8601 timestamp when the conversation started.                                                                           |
| `updatedAt`    | `string` | ISO 8601 timestamp of the most recent activity.                                                                             |

***

### Message Object

| Field            | Type              | Description                                                                                    |
| ---------------- | ----------------- | ---------------------------------------------------------------------------------------------- |
| `id`             | `string`          | Blotato message ID.                                                                            |
| `conversationId` | `string or null`  | Blotato ID of the parent conversation.                                                         |
| `platform`       | `string`          | `instagram` or `facebook`.                                                                     |
| `direction`      | `string`          | `incoming` for messages you receive, `outgoing` for messages you send.                         |
| `senderId`       | `string or null`  | Social platform (e.g. Instagram) ID of the sender.                                             |
| `recipientId`    | `string`          | Social platform (e.g. Instagram) ID of the recipient.                                          |
| `text`           | `string`          | Message text.                                                                                  |
| `payload`        | `object or null`  | Rich content on the message. See [Payload](#api-api-reference-messages--payload).              |
| `status`         | `string`          | Message status. See [Status Values](#api-api-reference-messages--status-values).               |
| `errorCode`      | `integer or null` | Set only when status is `failed`. See [Messaging Errors](/support/errors.md#messaging-errors). |
| `errorMessage`   | `string or null`  | Set only when status is `failed`.                                                              |
| `createdAt`      | `string`          | ISO 8601 timestamp when the message was created.                                               |

#### Payload

`payload` is `null` on a plain-text message. Otherwise it holds one or more of:

| Field          | Type     | Description                                                                                                                                                                                                                   |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attachment`   | `object` | Buttons you attached to an outgoing message. See [Buttons](#api-api-reference-messages--buttons).                                                                                                                             |
| `quickReplies` | `array`  | Quick-reply chips you attached to an outgoing message. See [Quick Replies](#api-api-reference-messages--quick-replies).                                                                                                       |
| `selection`    | `object` | Set on an incoming message where the person tapped a postback button or a quick reply. Holds `type` (`postback` or `quick-reply`) and your `payload` string. See [Reading a Tap](#api-api-reference-messages--reading-a-tap). |

#### Status Values

An outgoing message you send moves through `queued`, `processing`, then `sent`, or `failed` if it does not go through. An incoming message you receive shows as `delivered`.

| Status       | Meaning                               |
| ------------ | ------------------------------------- |
| `queued`     | Accepted and waiting to send.         |
| `processing` | Sending to the social platform.       |
| `sent`       | Handed to the social platform.        |
| `delivered`  | An incoming message delivered to you. |
| `failed`     | Did not send. See `errorMessage`.     |

#### Rate Limits

| Endpoint                             | Limit    |
| ------------------------------------ | -------- |
| `GET /conversations`                 | 60 / min |
| `GET /conversations/:conversationId` | 60 / min |
| `GET /messages`                      | 60 / min |
| `GET /messages/:messageId`           | 60 / min |
| `POST /messages`                     | 30 / min |

***

## DM Automations /v2/dm-automations

A DM automation sends a direct message when someone comments on your post or sends you a message. Blotato supports DM automations on Instagram and Facebook Pages. Twitter/X, TikTok, LinkedIn, Pinterest, Threads, Bluesky, and YouTube are not supported at this time.

Nine endpoints let you manage automations and inspect their activity:

* [List DM Automations](#api-api-reference-dm-automations--list-dm-automations) — `GET /v2/dm-automations`
* [Get DM Automation](#api-api-reference-dm-automations--get-dm-automation) — `GET /v2/dm-automations/:id`
* [Create DM Automation](#api-api-reference-dm-automations--create-dm-automation) — `POST /v2/dm-automations`
* [Update DM Automation](#api-api-reference-dm-automations--update-dm-automation) — `PATCH /v2/dm-automations/:id`
* [Update DM Automation Trigger](#api-api-reference-dm-automations--update-dm-automation-trigger) — `PATCH /v2/dm-automations/:flowId/triggers/:triggerId`
* [Delete DM Automation](#api-api-reference-dm-automations--delete-dm-automation) — `DELETE /v2/dm-automations/:id`
* [List Runs](#api-api-reference-dm-automations--list-runs) — `GET /v2/dm-automations/:id/runs`
* [List Logs](#api-api-reference-dm-automations--list-logs) — `GET /v2/dm-automations/:id/logs`
* [Get Analytics](#api-api-reference-dm-automations--get-analytics) — `GET /v2/dm-automations/:id/analytics`

For the web app walkthrough, see [DM Automations](/web-app-features/dm-automations.md).

### Before You Start

Blotato needs permission to read comments and read and send messages on your account.

If you connected your account before comments, messaging, or (for Instagram) follow gating launched, reconnect it so Blotato has the new permissions.

* For Instagram, see [Connect Instagram](/settings/social-accounts/instagram.md).
* For Facebook, see [Connect Facebook](/settings/social-accounts/facebook.md).

For Instagram comment triggers, the connected Instagram account must own the post. DM automations do not run on posts where your account appears only as a collaborator. If you co-authored a post, create the automation under the Instagram account that originally published the post.

### How an Automation Runs

1. Someone comments on your post or sends your account a message.
2. Blotato matches the event against every active trigger on every live automation on the account.
3. A match starts one **run** per matching automation. An automation with a comment trigger and a message trigger starts a single run per event.
4. If the automation has a follow gate or email gate, Blotato waits for the contact to complete it. On automations published after September 14, 2026, a contact with an email already on file skips the email gate.
5. Blotato sends the DM, then calls the webhook if one is configured.
6. The run reaches `completed`, `failed`, `expired`, or `superseded`.

Every live automation whose trigger matches starts its own run. Two automations matching the same comment both try to answer it, one reply reaches the contact, and the other run fails. Two automations matching the same message both send. Each matching comment also starts a new run, so a contact who comments the keyword twice gets two replies.

A comment trigger answers with a **private reply to the comment**. A message trigger answers with a standard DM.

Your own comments and the messages your account sends never start a run.

Comments on Instagram collaborator posts do not start a run for the collaborator account. Use the owner account for the automation.

#### Testing a Comment Trigger

Test from a different Instagram or Facebook account. A comment posted by the same account connected to the automation is skipped, even when its text matches a trigger keyword.

#### Optional Steps

Set `followGate`, `emailGate`, or `webhook` to extend the run. Blotato runs the steps in a fixed order:

1. **Follow gate** (`followGate`, Instagram only). Sends the gate message with a confirm button, waits up to 30 days for a reply, then checks whether the contact follows the account. A contact who does not follow gets the gate message again.
2. **Email gate** (`emailGate`). Sends the gate message, waits up to 72 hours for a reply, and reads the first email address out of it. A reply holding no email address returns the gate message. A match saves to the contact. On automations published after September 14, 2026, a contact with an email already on file skips the gate. See [Email Gate Object](#api-api-reference-dm-automations--email-gate-object).
3. **Your message** (`dmMessage` plus `buttons`).
4. **Webhook** (`webhook`). Calls your endpoint after the message sends.

A run waiting on a gate reaches `expired` when the contact never answers inside the window, and `superseded` when a newer run starts waiting on the same contact.

While a run waits on a contact's reply, their next DM resumes the waiting run instead of starting a new run. A button tap never starts a run.

***

### List DM Automations

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/dm-automations`

**Method:** `GET`

#### Description

Returns your DM automations, ordered by creation time (most recent first). Use cursor-based pagination. Archived automations do not appear. Every item includes run counts from the last 24 hours and the time it last ran.

#### Query Parameters

| Field    | Type      | Required | Description                                                      |
| -------- | --------- | -------- | ---------------------------------------------------------------- |
| `limit`  | `integer` | No       | Maximum automations to return (1-250). Default 50.               |
| `cursor` | `string`  | No       | Cursor from a previous response. Pass it to fetch the next page. |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "flow_abc123",
      "accountId": "98434",
      "name": "Auto-DM links from comments",
      "platform": "instagram",
      "target": { "targetType": "instagram" },
      "triggers": [
        {
          "id": "trg_abc123",
          "type": "comment-received",
          "keywords": ["price", "link"],
          "postId": "post_xyz789",
          "isActive": true
        },
        {
          "id": "trg_def456",
          "type": "message-received",
          "keywords": ["price", "link"],
          "isActive": true
        }
      ],
      "dmMessage": "Thanks for the interest! Tap below for the link.",
      "buttons": [],
      "isActive": true,
      "publishedVersionId": "ver_001",
      "createdAt": "2026-08-01T12:00:00Z",
      "updatedAt": "2026-08-02T09:30:00Z",
      "stats": {
        "triggered": 18,
        "completed": 16,
        "failed": 2,
        "lastRunAt": "2026-09-30T14:22:00Z"
      }
    }
  ],
  "cursor": "eyJz..."
}
```

| Field                     | Type             | Description                                                                                               |
| ------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
| `items`                   | `array`          | List of automations. See [DM Automation Object](#api-api-reference-dm-automations--dm-automation-object). |
| `items[].stats`           | `object`         | Run counts from the last 24 hours: `triggered`, `completed`, and `failed`, plus `lastRunAt`.              |
| `items[].stats.lastRunAt` | `string or null` | ISO 8601 time the automation last ran. Null when it has never run.                                        |
| `cursor`                  | `string`         | Cursor for the next page. Absent when there are no more automations.                                      |

For all-time totals and failures from the last 7 days, use [Get Analytics](#api-api-reference-dm-automations--get-analytics).

***

### Get DM Automation

#### Endpoint

**URL:** `/dm-automations/:id`

**Method:** `GET`

#### Description

Fetches a single automation by its Blotato ID, including its triggers and message. An archived automation still returns here with `isActive: false`.

#### Path Parameters

| Field | Type     | Required | Description                   |
| ----- | -------- | -------- | ----------------------------- |
| `id`  | `string` | Yes      | Blotato ID of the automation. |

#### Response

**Status Code:** `200 OK`

```json
{ "flow": { "id": "flow_abc123", "...": "..." } }
```

The `flow` object is a [DM Automation Object](#api-api-reference-dm-automations--dm-automation-object).

***

### Create DM Automation

#### Endpoint

**URL:** `/dm-automations`

**Method:** `POST`

#### Description

Creates a DM automation. When any of its triggers fires, the automation sends one direct message: text plus up to 3 link buttons. Pass up to 2 triggers in `triggers`: one `comment-received` trigger and one `message-received` trigger. One matching comment or message starts a single run. Optional fields gate the message behind an Instagram follow check, ask for an email address first, or call a webhook after the message sends.

* Set `followGate` (Instagram only) to gate the message behind a follow check.
* Set `emailGate` to gate it behind the contact replying with an email address.
* Set `webhook` to call an external endpoint once the message sends.

An automation is created as a draft unless you pass `isActive: true`. Inspect existing flows before creating another. Keep the example inactive until the user requests activation and approves the account, trigger scope, message, and link. Include only the requested triggers and optional steps.

#### Request Body

```json
{
  "accountId": "98434",
  "platform": "instagram",
  "target": { "targetType": "instagram" },
  "name": "Auto-DM links from comments",
  "triggers": [
    {
      "type": "comment-received",
      "keywords": ["price", "link"],
      "postId": null
    },
    {
      "type": "message-received",
      "keywords": ["price", "link"]
    }
  ],
  "dmMessage": "Here is the link you requested.",
  "buttons": [
    { "type": "url", "title": "Open link", "url": "https://example.com" }
  ],
  "isActive": false
}
```

#### Response

**Status Code:** `201 Created`

```json
{ "flow": { "id": "flow_abc123", "isActive": false, "...": "..." } }
```

***

### Update DM Automation

#### Endpoint

**URL:** `/dm-automations/:id`

**Method:** `PATCH`

#### Description

Updates an automation. Each field present in the patch replaces that field. Omitted fields stay unchanged. `triggers` replaces the whole trigger set, and `[]` clears it. Pass `null` for `followGate`, `emailGate`, or `webhook` to remove it.

The connected account, platform, and target are fixed at creation. To run on a different account, create a new automation.

#### Request Body

The patch is nested under a `patch` key.

```json
{
  "patch": {
    "dmMessage": "New reply text",
    "buttons": []
  }
}
```

| Field        | Type             | Required | Description                                                                                                                                                                 |
| ------------ | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`       | `string`         | No       | New automation label, 1-60 characters.                                                                                                                                      |
| `triggers`   | `array`          | No       | Replaces every trigger. Up to 2 triggers, at most one per type. Pass `[]` to remove every trigger. See [Trigger Object](#api-api-reference-dm-automations--trigger-object). |
| `dmMessage`  | `string`         | No       | Replaces the message text, 1-640 characters.                                                                                                                                |
| `buttons`    | `array`          | No       | Replaces the buttons. Pass `[]` to remove every button.                                                                                                                     |
| `followGate` | `object or null` | No       | Replaces the follow gate. Pass `null` to remove it. Instagram only. See [Follow Gate Object](#api-api-reference-dm-automations--follow-gate-object).                        |
| `emailGate`  | `object or null` | No       | Replaces the email gate. Pass `null` to remove it. See [Email Gate Object](#api-api-reference-dm-automations--email-gate-object).                                           |
| `webhook`    | `object or null` | No       | Replaces the webhook. Pass `null` to remove it. See [Webhook Object](#api-api-reference-dm-automations--webhook-object).                                                    |
| `isActive`   | `boolean`        | No       | `true` publishes the automation. `false` moves it to draft. Omit to keep the current state.                                                                                 |

Content changes to a live automation publish as soon as the request succeeds. Content changes to a draft stay saved until you pass `isActive: true`. A run already in progress keeps the version it started with.

A live automation needs at least one active trigger. To clear every trigger, pass an empty `triggers` array and set `isActive: false` in the same request. To remove one trigger, pass `triggers` holding the trigger you keep. To update a single trigger without other changes, for example to turn it off, use [Update DM Automation Trigger](#api-api-reference-dm-automations--update-dm-automation-trigger).

Updating an automation's content or triggers reissues its trigger IDs. Read `triggers` from the response before you call [Update DM Automation Trigger](#api-api-reference-dm-automations--update-dm-automation-trigger).

#### Response

**Status Code:** `200 OK`

```json
{ "flow": { "id": "flow_abc123", "...": "..." } }
```

***

### Update DM Automation Trigger

#### Endpoint

**URL:** `/dm-automations/:flowId/triggers/:triggerId`

**Method:** `PATCH`

#### Description

Updates a single trigger on an automation, for example to turn it on or off. The change applies immediately on a live automation, with no republish. The other triggers, the message, and the optional steps stay as they are.

Read the trigger ID from the `triggers` array of the automation. Updating an automation's content or triggers reissues its trigger IDs, so fetch the automation again before you update a trigger.

#### Path Parameters

| Field       | Type     | Required | Description                                                |
| ----------- | -------- | -------- | ---------------------------------------------------------- |
| `flowId`    | `string` | Yes      | Blotato ID of the automation.                              |
| `triggerId` | `string` | Yes      | ID of the trigger, from the automation's `triggers` array. |

#### Request Body

The patch is nested under a `patch` key.

```json
{
  "patch": { "isActive": false }
}
```

| Field      | Type      | Required | Description                                        |
| ---------- | --------- | -------- | -------------------------------------------------- |
| `isActive` | `boolean` | Yes      | `true` turns the trigger on. `false` turns it off. |

A live automation needs at least one active trigger. Turning off its last active trigger returns `422` with error code 20303. To stop every trigger, move the automation to draft with [Update DM Automation](#api-api-reference-dm-automations--update-dm-automation).

A draft accepts any update. Blotato checks the active-trigger rule when you publish.

#### Response

**Status Code:** `200 OK`

```json
{
  "trigger": {
    "id": "trg_abc123",
    "type": "comment-received",
    "keywords": ["price", "link"],
    "postId": null,
    "isActive": false
  }
}
```

The `trigger` object is a [Trigger Object](#api-api-reference-dm-automations--trigger-object). A `404` means the automation or the trigger ID was not found.

***

### Delete DM Automation

#### Endpoint

**URL:** `/dm-automations/:id`

**Method:** `DELETE`

#### Description

Archives an automation and stops new runs. Existing runs, including waiting gates, remain able to continue. Archiving does not cancel messages already queued. Archive only when the user requests it.

The archived automation leaves [List DM Automations](#api-api-reference-dm-automations--list-dm-automations), but [Get DM Automation](#api-api-reference-dm-automations--get-dm-automation) still returns it. Publishing it again with [Update DM Automation](#api-api-reference-dm-automations--update-dm-automation) and `isActive: true` restores it.

#### Response

**Status Code:** `200 OK`

Returns the archived [DM Automation Object](#api-api-reference-dm-automations--dm-automation-object).

***

### List Runs

#### Endpoint

**URL:** `/dm-automations/:id/runs`

**Method:** `GET`

#### Description

Returns the execution runs of one automation, ordered by activity time (most recent first). Use this endpoint to confirm an automation fired and inspect failed runs.

#### Query Parameters

| Field    | Type       | Required | Description                                                                                                                                       |
| -------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`  | `integer`  | No       | Maximum runs to return (1-250). Default 50.                                                                                                       |
| `cursor` | `string`   | No       | Cursor from a previous response. Pass it to fetch the next page.                                                                                  |
| `status` | `string[]` | No       | Include runs matching any supplied status: `running`, `waiting`, `completed`, `expired`, `superseded`, or `failed`. Omit to include every status. |
| `since`  | `string`   | No       | Include runs with activity at or after this ISO 8601 timestamp.                                                                                   |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "run_abc123",
      "contactId": "1784000000",
      "platform": "instagram",
      "status": "failed",
      "error": {
        "code": 20102,
        "message": "Messaging window has expired",
        "action": "Ask the contact to send a new message, then retry.",
        "details": { "retryable": false }
      },
      "createdAt": "2026-08-02T09:30:00Z",
      "updatedAt": "2026-08-02T09:30:04Z"
    }
  ],
  "cursor": "eyJz..."
}
```

See [Run Object](#api-api-reference-dm-automations--run-object).

***

### List Logs

#### Endpoint

**URL:** `/dm-automations/:id/logs`

**Method:** `GET`

#### Description

Returns logs written as an automation's runs progress, ordered by creation time (most recent first). Pass `flowRunId` to narrow the response to one run.

#### Query Parameters

| Field       | Type      | Required | Description                                                                |
| ----------- | --------- | -------- | -------------------------------------------------------------------------- |
| `flowRunId` | `string`  | No       | Include logs belonging to this run. Omit to include logs across every run. |
| `limit`     | `integer` | No       | Maximum logs to return (1-250). Default 50.                                |
| `cursor`    | `string`  | No       | Cursor from a previous response. Pass it to fetch the next page.           |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "log_abc123",
      "flowRunId": "run_abc123",
      "nodeId": "nd_9fJ2",
      "level": "info",
      "message": "Queued message for send",
      "context": { "messageId": "msg_abc123" },
      "createdAt": "2026-08-02T09:30:01Z"
    }
  ],
  "cursor": "eyJz..."
}
```

See [Log Object](#api-api-reference-dm-automations--log-object).

***

### Get Analytics

#### Endpoint

**URL:** `/dm-automations/:id/analytics`

**Method:** `GET`

#### Description

Returns all-time run totals and the number of recent failures for one automation.

#### Response

**Status Code:** `200 OK`

```json
{
  "analytics": {
    "triggered": 412,
    "completed": 398,
    "failed": 9,
    "issues": 3
  }
}
```

| Field       | Type      | Description                                                            |
| ----------- | --------- | ---------------------------------------------------------------------- |
| `triggered` | `integer` | Runs started by a matching comment or message.                         |
| `completed` | `integer` | Runs where every step finished.                                        |
| `failed`    | `integer` | All-time runs ending on an error.                                      |
| `issues`    | `integer` | Runs that failed during the last 7 days. This is a subset of `failed`. |

Runs still in progress and runs ending as `expired` or `superseded` count toward `triggered`, but not `completed` or `failed`. The sum of `completed` and `failed` is sometimes lower than `triggered`.

***

### DM Automation Object

| Field                | Type             | Description                                                                                                                                               |
| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | `string`         | Blotato automation ID.                                                                                                                                    |
| `accountId`          | `string`         | ID of the connected account the automation runs on.                                                                                                       |
| `name`               | `string`         | Automation label.                                                                                                                                         |
| `platform`           | `string`         | `instagram` or `facebook`.                                                                                                                                |
| `target`             | `object`         | See [Target](#api-api-reference-dm-automations--target).                                                                                                  |
| `triggers`           | `array`          | Triggers the automation listens for, up to 2. Empty on a draft with no triggers. See [Trigger Object](#api-api-reference-dm-automations--trigger-object). |
| `dmMessage`          | `string`         | Message text sent when a trigger fires.                                                                                                                   |
| `buttons`            | `array`          | Link buttons attached to the message. See [Button Object](#api-api-reference-dm-automations--button-object).                                              |
| `followGate`         | `object`         | Follow gate on the automation. Absent when no follow gate is set. See [Follow Gate Object](#api-api-reference-dm-automations--follow-gate-object).        |
| `emailGate`          | `object`         | Email gate on the automation. Absent when no email gate is set. See [Email Gate Object](#api-api-reference-dm-automations--email-gate-object).            |
| `webhook`            | `object`         | Webhook on the automation. Absent when no webhook is set. See [Webhook Object](#api-api-reference-dm-automations--webhook-object).                        |
| `isActive`           | `boolean`        | `true` when the automation is live and listening.                                                                                                         |
| `publishedVersionId` | `string or null` | ID of the published version. `null` for a draft.                                                                                                          |
| `createdAt`          | `string`         | ISO 8601 timestamp when the automation was created.                                                                                                       |
| `updatedAt`          | `string`         | ISO 8601 timestamp of the most recent edit.                                                                                                               |

#### Target

| Field        | Type     | Required     | Description                                                                                                                                 |
| ------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `targetType` | `string` | Yes          | `instagram` or `facebook`.                                                                                                                  |
| `pageId`     | `string` | For Facebook | ID of the Facebook Page, from [subaccounts](#api-api-reference-accounts--list-subaccounts-pages). Required when `targetType` is `facebook`. |

#### Trigger Object

An automation holds up to 2 triggers: one `comment-received` trigger and one `message-received` trigger. Each trigger carries its own keywords, and a comment trigger carries its own `postId`. One matching comment or message starts a single run.

| Field      | Type             | Required  | Description                                                                                                                                                                                                                |
| ---------- | ---------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`       | `string`         | Read-only | Blotato trigger ID. Present on every trigger Blotato returns. Omit it when you create or update an automation. Pass it to [Update DM Automation Trigger](#api-api-reference-dm-automations--update-dm-automation-trigger). |
| `type`     | `string`         | Yes       | `comment-received` or `message-received`.                                                                                                                                                                                  |
| `keywords` | `array`          | Yes       | Words or phrases the comment or message must contain. Pass `[]` to fire on every comment or text message. A message without text, such as a photo or sticker, never fires.                                                 |
| `postId`   | `string or null` | No        | Comment triggers only. Blotato ID of the tracked post to match. Do not use the platform's native post ID. `null` watches every post and reel on the account.                                                               |
| `isActive` | `boolean`        | No        | `false` turns this trigger off while the automation stays live. Default `true`.                                                                                                                                            |

Blotato enforces these rules when an automation goes live and while it stays live:

* At least one active trigger.
* At most one trigger per type.

A draft holds any trigger set, including an empty one. Blotato rejects a request with more than 2 triggers.

Updating an automation's content or triggers reissues its trigger IDs. Read `triggers` from the response before you update a trigger.

Keyword matching ignores case, matches anywhere in the text, and tolerates line breaks inside a multi-word keyword. For example, `price` matches both `price` and `pricey`. A comment or message fires the automation when it contains any one of the keywords.

The trigger matches a Blotato post ID, not publication origin. [List Published Posts](#api-api-reference-list-published-posts) and the web post picker list Blotato-published posts only. For an Instagram post published outside Blotato, an incoming comment creates a tracked post record. Retrieve the comment through [List Comments](#api-api-reference-comments--list-comments), verify its account and native `platformPostId`, and use its non-null `postId` for the trigger. The request still needs the connected account and platform containing this post.

If the required Blotato post ID is unavailable, stop and explain the missing ID. Do not replace it with a native ID or widen a single-post request to all posts. Use `postId: null` with a keyword only when the user approves matching across the account's posts and reels.

#### Button Object

| Field   | Type     | Required       | Description                                              |
| ------- | -------- | -------------- | -------------------------------------------------------- |
| `type`  | `string` | Yes            | `url`. Link buttons are the only type `buttons` accepts. |
| `title` | `string` | Yes            | Button label, up to 20 characters.                       |
| `url`   | `string` | Yes to publish | `http(s)` URL the button opens.                          |

To send quick-reply chips or postback buttons, use [Send Message](#api-api-reference-messages--buttons-and-quick-replies).

**Instagram renders buttons in the Instagram mobile app only.** A recipient reading the DM on instagram.com in a desktop browser sees the message text with no buttons. To work around this issue, you can add the URL directly to the message text, and Instagram will render it as a clickable link. Set `buttons` or put a URL inside `dmMessage`, not both, since a message carrying both leaves the URL unclickable on desktop. For a desktop audience, pass `buttons: []` and put the URL in `dmMessage`. See [Instagram Limitations](/social-accounts-and-platform-faqs/instagram/limitations.md#buttons-and-quick-replies).

#### Follow Gate Object

Instagram only. Holds `dmMessage` back until the contact follows the account.

| Field         | Type     | Required | Description                                                                           |
| ------------- | -------- | -------- | ------------------------------------------------------------------------------------- |
| `message`     | `string` | Yes      | Gate message text, 1-640 characters. Blotato sends it with a confirm button under it. |
| `buttonTitle` | `string` | No       | Label on the confirm button, up to 20 characters. Default `I'm following`.            |

Blotato sends the gate message, waits up to 30 days for a reply, then reads the contact's follower status. A contact who follows receives `dmMessage`. A contact who does not receives the gate message again.

* **The gate message always goes first.** Instagram grants access to follower status once the contact opens a DM thread with the account, so the gate message precedes the check.
* **An unknown follower status sends `dmMessage`.** Instagram withholds follower status for a contact who never granted profile access, and Blotato proceeds rather than blocking them.
* **A confirmed follow lasts 30 days.** The gate message still goes out on every run. Blotato reuses a confirmed follow for 30 days, so a repeat contact's reply passes without a new check. A negative result is never reused.
* **The confirm button is a postback button Blotato manages.** You set its label only. See [Instagram Limitations](/social-accounts-and-platform-faqs/instagram/limitations.md#dm-automations).
* **Any text reply advances the gate.** Blotato runs the follower check on the contact's next text DM, whatever it says. A photo, sticker, or voice note does not count. The tap is a shortcut, not a requirement.
* **The account needs a button-tap subscription.** Blotato subscribes the account at connect time. An account connected before DM automations and follow gating landed does not reliably receive a tap, and runs reach `expired`. Reconnect the account to fix it.
* **Instagram renders the confirm button in the mobile app only.** A contact reading on instagram.com in a desktop browser sees the gate message with nothing to tap. Write `message` to ask for a typed reply, for example "Reply FOLLOWING once you have", so a desktop audience still advances.

#### Email Gate Object

Holds `dmMessage` back until the contact replies with an email address.

| Field     | Type     | Required | Description                          |
| --------- | -------- | -------- | ------------------------------------ |
| `message` | `string` | Yes      | Gate message text, 1-640 characters. |

Blotato sends the gate message, waits up to 72 hours for a reply, and reads the first email address out of it. A reply holding no email address returns the gate message and Blotato waits again. A match saves to the contact and `dmMessage` sends.

**A contact with an email already on file skips the gate.** Blotato sends `dmMessage` without the gate message. An email lands on file when the contact replies with an address to any email gate on the same connected account.

This applies to automations published after September 14, 2026. An automation published earlier sends the gate message every time until you save it again. Pass its current `triggers` to [Update DM Automation](#api-api-reference-dm-automations--update-dm-automation), or click **Save and Publish** in the web app.

Blotato checks the shape of the address, not whether the mailbox exists. Read a captured address through the [Webhook Object](#api-api-reference-dm-automations--webhook-object).

#### Webhook Object

Endpoint Blotato calls after `dmMessage` sends.

| Field     | Type     | Required | Description                                                                  |
| --------- | -------- | -------- | ---------------------------------------------------------------------------- |
| `method`  | `string` | Yes      | `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`.                                  |
| `url`     | `string` | Yes      | Public `http(s)` endpoint, 1-2048 characters.                                |
| `headers` | `object` | No       | String keys and string values sent with the request, for example an API key. |

Every method except `GET` carries a JSON body with `Content-Type: application/json`. `GET` carries no body.

```json
{ "email": "them@example.com", "isFollower": true }
```

| Field        | Type              | Description                                                                                       |
| ------------ | ----------------- | ------------------------------------------------------------------------------------------------- |
| `email`      | `string or null`  | Email address on file for the contact, captured by an email gate. `null` when Blotato holds none. |
| `isFollower` | `boolean or null` | Follower status a follow gate last recorded for the contact. `null` when Blotato never checked.   |

Both keys are present on every request except `GET`, with or without a gate on the automation.

| Rule           | Behavior                                                                                                                     |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Address        | The host must resolve to a public address. Private, loopback, link-local, and cloud metadata ranges return error code 20304. |
| Protocol       | `http` and `https` only.                                                                                                     |
| Redirects      | Blotato does not follow them. Point the automation at the final URL.                                                         |
| Timeout        | 15 seconds. A timeout logs a warning with error code 20305, the run still completes, and Blotato does not retry the request. |
| Response body  | Blotato reads the first 16 KB.                                                                                               |
| Error response | A non-2xx status gets logged with its status, and the run still completes.                                                   |
| Failure        | A blocked address, a DNS failure, or a connection error fails the run with error code 20304.                                 |
| Headers        | Redacted from run logs. The URL and body appear in the logs, so send keys in a header, not in the URL.                       |

This webhook is separate from the [webhook publish target](#api-api-reference-publish-post), which sends post content to your endpoint at publish time.

#### Run Object

| Field       | Type     | Description                                                                                                                   |
| ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `id`        | `string` | Blotato run ID. Pass it as `flowRunId` to [List Logs](#api-api-reference-dm-automations--list-logs).                          |
| `contactId` | `string` | Social platform ID of the person who triggered the run.                                                                       |
| `platform`  | `string` | `instagram` or `facebook`.                                                                                                    |
| `status`    | `string` | See [Run Status Values](#api-api-reference-dm-automations--run-status-values).                                                |
| `error`     | `object` | Set only when status is `failed`. Holds `code`, `message`, and, when available, `action` and `details` with recovery context. |
| `createdAt` | `string` | ISO 8601 timestamp when the run started.                                                                                      |
| `updatedAt` | `string` | ISO 8601 timestamp of the most recent run update.                                                                             |

**Run Status Values**

| Status       | Meaning                                                                                                                                                                                                |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `running`    | Executing a step.                                                                                                                                                                                      |
| `waiting`    | Waiting for the DM to settle, or waiting on the contact to answer a gate.                                                                                                                              |
| `completed`  | Every step finished, including the DM.                                                                                                                                                                 |
| `expired`    | The contact never answered a gate inside its window: 30 days for a follow gate, 72 hours for an email gate. A run also expires when its message never settles within 30 minutes. Nothing further sent. |
| `superseded` | A newer run started waiting on the same contact, so this one stopped.                                                                                                                                  |
| `failed`     | The run stopped on an error, either before or after the DM went out. Read `error`.                                                                                                                     |

#### Log Object

| Field       | Type             | Description                                                     |
| ----------- | ---------------- | --------------------------------------------------------------- |
| `id`        | `string`         | Blotato log ID.                                                 |
| `flowRunId` | `string`         | ID of the run this log belongs to.                              |
| `nodeId`    | `string or null` | ID of the step the log came from. `null` for run-level entries. |
| `level`     | `string`         | `info`, `warning`, or `error`.                                  |
| `message`   | `string`         | Description of the step.                                        |
| `context`   | `object`         | Step-specific details, for example the message ID.              |
| `createdAt` | `string`         | ISO 8601 timestamp when the log was written.                    |

***

### Platform Rules

Instagram and Facebook set these rules, not Blotato. A message outside an allowed window reaches status `failed` on the run.

* **Comment trigger.** Blotato answers with a private reply to the comment. Both platforms allow one private reply per comment, within 7 days of the comment. A comment that already received a private reply, through Blotato or another tool, rejects the second reply.
* **Message trigger.** Blotato replies within 24 hours of the person's last message.
* **No cold outreach.** An automation only answers people who comment or message first.
* **A reply during a gate continues the run.** While a run waits on a contact's answer, their next DM resumes the waiting run instead of starting a new one. A button tap never starts a run.

See [Messaging Windows](#api-api-reference-messages--messaging-windows).

### Active Contacts

Every DM an automation sends counts toward your monthly active-contacts limit, the same as a message sent through [Send Message](#api-api-reference-messages--send-message). Reaching the same person twice in a month counts once.

See [Active Contacts](/settings/billing-and-credits.md#active-contacts).

***

### Errors

| Status | Reason                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `404`  | The automation was not found. Returned by [Get DM Automation](#api-api-reference-dm-automations--get-dm-automation), [Update DM Automation](#api-api-reference-dm-automations--update-dm-automation), [Update DM Automation Trigger](#api-api-reference-dm-automations--update-dm-automation-trigger), and [Delete DM Automation](#api-api-reference-dm-automations--delete-dm-automation). [Update DM Automation Trigger](#api-api-reference-dm-automations--update-dm-automation-trigger) also returns it for an unknown trigger ID. An archived automation still returns `200` from Get, Update, and Delete. |
| `422`  | The automation is invalid for publishing (error code 20303), or the connected account is missing (error code 5000). Setting `followGate` on a `facebook` automation returns 20303. So does a live automation with no trigger, two triggers of one type, or no active trigger.                                                                                                                                                                                                                                                                                                                                   |
| `500`  | Unexpected server error.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |

A webhook failure surfaces on the run, not on the request. A blocked address, a DNS failure, or a connection error fails the run with error code 20304. A timeout logs a warning with error code 20305, and the run still completes. See [Error Reference](/support/errors.md#dm-automation-errors).

### Rate Limits

| Endpoint                                            | Limit    |
| --------------------------------------------------- | -------- |
| `GET /dm-automations`                               | 60 / min |
| `GET /dm-automations/:id`                           | 60 / min |
| `POST /dm-automations`                              | 30 / min |
| `PATCH /dm-automations/:id`                         | 30 / min |
| `PATCH /dm-automations/:flowId/triggers/:triggerId` | 30 / min |
| `DELETE /dm-automations/:id`                        | 30 / min |
| `GET /dm-automations/:id/runs`                      | 60 / min |
| `GET /dm-automations/:id/logs`                      | 60 / min |
| `GET /dm-automations/:id/analytics`                 | 60 / min |

***

## Create Source /v2/source-resolutions-v3

Submit content for extraction and receive a source ID for polling.

### Endpoint

```
POST https://backend.blotato.com/v2/source-resolutions-v3
```

**Rate Limit:** 30 requests / minute

### Authentication

Include your Blotato API key in the request headers:

```
blotato-api-key: YOUR_API_KEY
```

### Source Types

| Value              | Requires | Description                                              |
| ------------------ | -------- | -------------------------------------------------------- |
| `youtube`          | `url`    | Extract YouTube transcript (English captions only)       |
| `tiktok`           | `url`    | Extract TikTok transcript (English captions only)        |
| `article`          | `url`    | Extract article text from a web page                     |
| `pdf`              | `url`    | Extract text from a PDF                                  |
| `audio`            | `url`    | Transcribe audio (mp3, wav, m4a, ogg, flac, aac)         |
| `twitter`          | `url`    | Extract tweet content                                    |
| `text`             | `text`   | Transform raw text content with optional AI instructions |
| `perplexity-query` | `text`   | AI-powered web research query                            |

### Parameters

| Parameter            | Type   | Required                                   | Description                                                                                   |
| -------------------- | ------ | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `source.sourceType`  | string | Yes                                        | One of: `youtube`, `tiktok`, `article`, `pdf`, `audio`, `twitter`, `text`, `perplexity-query` |
| `source.url`         | string | Required for URL-based types               | The URL to extract content from                                                               |
| `source.text`        | string | Required for `text` and `perplexity-query` | Raw text or search query                                                                      |
| `customInstructions` | string | No                                         | AI instructions to transform extracted content                                                |

### Examples

#### Extract YouTube Transcript

```json
{
  "source": {
    "sourceType": "youtube",
    "url": "https://www.youtube.com/watch?v=VIDEO_ID"
  }
}
```

YouTube and TikTok transcript extraction reads **English captions only**. A non-English video, or a video with no captions, returns a `failed` status. For those videos, pass the transcript or a summary using `sourceType: "text"` instead.

#### Extract Article with Custom Instructions

```json
{
  "source": {
    "sourceType": "article",
    "url": "https://example.com/article"
  },
  "customInstructions": "Summarize in 5 bullet points"
}
```

#### AI Research Query

```json
{
  "source": {
    "sourceType": "perplexity-query",
    "text": "latest AI trends in social media marketing"
  }
}
```

#### Transform Raw Text

```json
{
  "source": {
    "sourceType": "text",
    "text": "Your raw content here..."
  },
  "customInstructions": "Rewrite as a Twitter thread"
}
```

### Response

```json
{
  "id": "123e4567-e89b-12d3-a456-426614174000"
}
```

Use this ID with the [Get Source](#api-api-reference-get-source) endpoint to retrieve the extracted content.

### n8n and Make.com

1. In n8n, select Source > Create.
2. Save the returned `id`.
3. Use Source > Get to check the saved ID until `status` is `completed` or `failed`.
4. Read `content` after `completed`, or report `message` after `failed`.

Follow the [n8n polling instructions](/integrations-and-automation-templates/n8n/n8n-blotato-node.md#using-source-operations) or the [source-to-publication workflow](/integrations-and-automation-templates/templates/11-build-your-first-ai-automation.md).

For Make, follow the [Make setup guide](/integrations-and-automation-templates/make.com.md). Inspect the installed module's fields or use the [async REST contract](#api-recipes-workflows). n8n operation labels do not establish Make module labels.

***

## Get Source /v2/source-resolutions-v3/:id

Retrieve extracted content by source ID.

### Endpoint

```
GET https://backend.blotato.com/v2/source-resolutions-v3/:id
```

### Authentication

Include your Blotato API key in the request headers:

```
blotato-api-key: YOUR_API_KEY
```

### Parameters

| Parameter | Type   | Required | Description                                        |
| --------- | ------ | -------- | -------------------------------------------------- |
| `id`      | string | Yes      | The source resolution ID (UUID) from Create Source |

### Clean Transcript Option

`cleanTranscript` is not a REST query parameter or an MCP tool argument. The n8n Blotato node's Source > Get operation provides a local Clean Transcript option, enabled by default. It processes the returned content after the API request.

For REST or MCP, retrieve the completed `content` first, then remove transcript timestamps in your agent or workflow if requested. Do not send `cleanTranscript` to this endpoint expecting it to change the response.

### Response

#### Processing (poll again)

```json
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "status": "processing"
}
```

#### Completed

```json
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "status": "completed",
  "title": "Video Title",
  "content": "Extracted text content...",
  "referenceUrl": "https://original-source-url.com"
}
```

#### Failed

```json
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "status": "failed",
  "message": "Error description"
}
```

### Status Values

| Status       | Description                              |
| ------------ | ---------------------------------------- |
| `queued`     | Source submitted, waiting to process     |
| `processing` | Extraction in progress                   |
| `completed`  | Extraction successful, content available |
| `failed`     | Extraction failed, check message field   |

### Polling Pattern

Source extraction is asynchronous. After calling Create Source:

1. Wait at least 10 seconds
2. Call Get Source with the ID
3. If status is `queued` or `processing`, wait and retry
4. If status is `completed`, use the `content` field
5. If status is `failed`, check the `message` field

### n8n and Make.com

In the official Blotato nodes:

1. Add a Blotato node
2. Select "Source" > "Get"
3. Pass in the source ID from "Create Source"
4. Add a Wait node of at least 10 seconds between status checks
5. The extracted content appears in the response

***

## Visual Templates

Create images and videos using pre-built templates with the [Create Visual API endpoint](#api-api-reference-create-video).

### How to Use Templates

1. Choose a template from the categories below
2. Retrieve the template through `blotato_list_visual_templates` or `GET /v2/videos/templates`.
3. Copy its exact `id` into `templateId`. Some IDs are UUIDs and others are paths. Do not shorten a path.
4. Send a POST request to `/v2/videos/from-templates` with `inputs: {}`, a `prompt`, and `render: true`.

#### Example Request

Use the `prompt` parameter to describe what you want. Set `inputs` to `{}`. AI fills in the template inputs automatically.

Use the exact returned template ID. For this quote-card template, use the full path below.

```http
POST https://backend.blotato.com/v2/videos/from-templates
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "templateId": "/base/v2/quote-card/77f65d2b-48cc-4adb-bfbb-5bc86f8c01bd/v1",
  "inputs": {},
  "prompt": "Create 5 motivational quotes about entrepreneurship",
  "render": true
}
```

#### LLM-Friendly Reference

For LLM integrations, see [API Reference for LLMs](/start-with-an-ai-agent/llm.md) for a plain text reference with all endpoints and parameters.

***

### Image Slideshows

For product images, see [Product Scene Placement](/rest-api-reference/visuals/f524614b-ba01-448c-967a-ce518c52a700.md).

Create image slideshows with text overlays. Images uploaded or AI-generated.

| Template                                                                                                  | ID                                                                 | Content input                   | Output    |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------- | --------- |
| [Image Slideshow with Text Overlays](/rest-api-reference/visuals/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f.md) | `/base/v2/image-slideshow/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f/v1` | `inputs.slides` (objects)       | Slideshow |
| [Instagram Carousel Slideshow](/rest-api-reference/visuals/53cfec04-2500-41cf-8cc1-ba670d2c341a.md)       | `53cfec04-2500-41cf-8cc1-ba670d2c341a`                             | `inputs.slidePrompts` (strings) | Slideshow |

The AI Video with AI Voice template uses `inputs.scenes`, not either slideshow field. [Inspect the selected template's inputs](#api-api-reference-create-video--inspect-inputs-without-generating-a-visual) before building a manual request.

***

### Quote Cards

Quote card carousels with various background styles.

| Template                                                                                                              | ID                                                            | Output    |
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------- |
| [Quote Card with Monocolor Background](/rest-api-reference/visuals/77f65d2b-48cc-4adb-bfbb-5bc86f8c01bd.md)           | `/base/v2/quote-card/77f65d2b-48cc-4adb-bfbb-5bc86f8c01bd/v1` | Slideshow |
| [Quote Card with Paper Background and Highlight](/rest-api-reference/visuals/f941e306-76f7-45da-b3d9-7463af630e91.md) | `/base/v2/quote-card/f941e306-76f7-45da-b3d9-7463af630e91/v1` | Slideshow |

***

### Tweet Cards

Twitter/X-style quote cards with minimal or photo/video backgrounds.

| Template                                                                                                      | ID                                                            | Output    |
| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------- |
| [Tweet Card with Minimal Style](/rest-api-reference/visuals/ba413be6-a840-4e60-8fd6-0066d3b427df.md)          | `/base/v2/tweet-card/ba413be6-a840-4e60-8fd6-0066d3b427df/v1` | Slideshow |
| [Tweet Card with Photo/Video Background](/rest-api-reference/visuals/9714ae5c-7e6b-4878-be4a-4b1ba5d0cd66.md) | `/base/v2/tweet-card/9714ae5c-7e6b-4878-be4a-4b1ba5d0cd66/v1` | Slideshow |

***

### Tutorial Carousels

Step-by-step tutorial visuals with customizable styling.

| Template                                                                                                            | ID                                                                   | Output    |
| ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------- |
| [Tutorial Carousel with Minimalist Flat Style](/rest-api-reference/visuals/2491f97b-1b47-4efa-8b96-8c651fa7b3d5.md) | `/base/v2/tutorial-carousel/2491f97b-1b47-4efa-8b96-8c651fa7b3d5/v1` | Slideshow |
| [Tutorial Carousel with Monocolor Background](/rest-api-reference/visuals/e095104b-e6c5-4a81-a89d-b0df3d7c5baf.md)  | `/base/v2/tutorial-carousel/e095104b-e6c5-4a81-a89d-b0df3d7c5baf/v1` | Slideshow |

***

### Images with Text

Combine images and text overlays in various styles.

| Template                                                                                                           | ID                                                                  | Output    |
| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | --------- |
| [Image Slideshow with Prominent Text](/rest-api-reference/visuals/0ddb8655-c3da-43da-9f7d-be1915ca7818.md)         | `/base/v2/images-with-text/0ddb8655-c3da-43da-9f7d-be1915ca7818/v1` | Slideshow |
| [When X then Y Text Slideshow](/rest-api-reference/visuals/c9892c3b-fa75-4ade-821a-a50ff8456230.md)                | `/base/v2/images-with-text/c9892c3b-fa75-4ade-821a-a50ff8456230/v1` | Video     |
| [Video of Images and Text with Minimal Style](/rest-api-reference/visuals/3ed4bb92-dbfe-45e6-9dc8-605b77f70506.md) | `/base/v2/images-with-text/3ed4bb92-dbfe-45e6-9dc8-605b77f70506/v1` | Video     |

***

### Video Editor

Combine and edit video clips with titles, captions, and music.

| Template                                                                                                   | ID                                                               | Output |
| ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------ |
| [Combine Clips and Apply Basic Edits](/rest-api-reference/visuals/c306ae43-1dcc-4f45-ac2b-88e75430ffd8.md) | `/base/v2/combine-clips/c306ae43-1dcc-4f45-ac2b-88e75430ffd8/v1` | Video  |

***

### AI Videos

AI-powered video generation with voiceovers and narration.

| Template                                                                                                                 | ID                                                                 | Output |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | ------ |
| [AI Video with AI Voice](/rest-api-reference/visuals/5903fe43-514d-40ee-a060-0d6628c5f8fd.md)                            | `/base/v2/ai-story-video/5903fe43-514d-40ee-a060-0d6628c5f8fd/v1`  | Video  |
| [AI Selfie Talking Video with Consistent Character](/rest-api-reference/visuals/57f5a565-fd17-458b-be43-4a2d8ccaca75.md) | `/base/v2/ai-selfie-video/57f5a565-fd17-458b-be43-4a2d8ccaca75/v1` | Video  |

***

### AI Avatar

AI avatar videos with generated B-roll footage.

| Template                                                                                                  | ID                                                                 | Output |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------ |
| [AI Avatar with AI Generated B-roll](/rest-api-reference/visuals/7c26a1cd-d5b3-42da-9c73-2413333873b3.md) | `/base/v2/ai-avatar-broll/7c26a1cd-d5b3-42da-9c73-2413333873b3/v1` | Video  |

***

### AI-Generated Infographics (V1 Legacy)

AI-powered infographic templates that generate single images based on text descriptions. Each template applies a unique visual style to your content.

#### News and Media

| Template                                                                                         | ID                                                     | Output |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ------ |
| [TV Wall Infographic](/rest-api-reference/visuals/013904bf-6b3b-43f4-bb1f-f1964a38c29b.md)       | `/video-template/013904bf-6b3b-43f4-bb1f-f1964a38c29b` | Image  |
| [Newspaper Infographic](/rest-api-reference/visuals/07a5b5c5-387c-49e3-86b1-de822cd2dfc7.md)     | `/video-template/07a5b5c5-387c-49e3-86b1-de822cd2dfc7` | Image  |
| [Breaking News](/rest-api-reference/visuals/8800be71-52df-4ac7-ac94-df9d8a494d0f.md)             | `/video-template/8800be71-52df-4ac7-ac94-df9d8a494d0f` | Image  |
| [Movie Theater Infographic](/rest-api-reference/visuals/b88c8273-6406-48c6-85e7-096119aefe30.md) | `/video-template/b88c8273-6406-48c6-85e7-096119aefe30` | Image  |

#### Urban and Street

| Template                                                                                          | ID                                                     | Output |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------ |
| [Graffiti Mural Infographic](/rest-api-reference/visuals/3598483b-c148-4276-a800-eede85c1c62f.md) | `/video-template/3598483b-c148-4276-a800-eede85c1c62f` | Image  |
| [Bus Ad Infographic](/rest-api-reference/visuals/f9c0e470-9288-4958-8cdd-64772ed93c05.md)         | `/video-template/f9c0e470-9288-4958-8cdd-64772ed93c05` | Image  |
| [Billboard Infographic](/rest-api-reference/visuals/76b3b959-bdbe-440d-8428-984219353f18.md)      | `/video-template/76b3b959-bdbe-440d-8428-984219353f18` | Image  |

#### Education

| Template                                                                                                | ID                                                     | Output |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------ |
| [Classroom Chalkboard Infographic](/rest-api-reference/visuals/d9495026-3945-44f6-8b44-07c28c492e6d.md) | `/video-template/d9495026-3945-44f6-8b44-07c28c492e6d` | Image  |
| [Whiteboard Infographic](/rest-api-reference/visuals/ae868019-820d-434c-8fe1-74c9da99129a.md)           | `/video-template/ae868019-820d-434c-8fe1-74c9da99129a` | Image  |
| [Chalkboard Infographic](/rest-api-reference/visuals/fcd64907-b103-46f8-9f75-51b9d1a522f5.md)           | `/video-template/fcd64907-b103-46f8-9f75-51b9d1a522f5` | Image  |

#### Outdoor

| Template                                                                                         | ID                                                     | Output |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ------ |
| [Trail Marker Infographic](/rest-api-reference/visuals/29ebb2bd-02b7-4317-8bb8-c30eb938e47c.md)  | `/video-template/29ebb2bd-02b7-4317-8bb8-c30eb938e47c` | Image  |
| [Constellation Infographic](/rest-api-reference/visuals/5307053e-046b-4c9b-b1ca-38725d2ddcdd.md) | `/video-template/5307053e-046b-4c9b-b1ca-38725d2ddcdd` | Image  |

#### Creative

| Template                                                                                          | ID                                                     | Output    |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------- |
| [Manga Panel Infographic](/rest-api-reference/visuals/49c61370-a706-4b82-98f7-62d557d1c66d.md)    | `/video-template/49c61370-a706-4b82-98f7-62d557d1c66d` | Image     |
| [T-Shirt Infographic](/rest-api-reference/visuals/476f8920-8749-4ff7-9c91-470d54c3c03e.md)        | `/video-template/476f8920-8749-4ff7-9c91-470d54c3c03e` | Image     |
| [Futuristic Flyer](/rest-api-reference/visuals/8fa8545e-8955-4a89-a868-cf45023d6cc5.md)           | `/video-template/8fa8545e-8955-4a89-a868-cf45023d6cc5` | Image     |
| [Book Page Infographic](/rest-api-reference/visuals/f8f1ebe4-a9f5-4ec8-be63-21214656cd4b.md)      | `/video-template/f8f1ebe4-a9f5-4ec8-be63-21214656cd4b` | Image     |
| [Single Centered Text Quote](/rest-api-reference/visuals/9f4e66cd-b784-4c02-b2ce-e6d0765fd4c0.md) | `/video-template/9f4e66cd-b784-4c02-b2ce-e6d0765fd4c0` | Slideshow |

#### Tech

| Template                                                                                      | ID                                                     | Output |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------ |
| [Steampunk Infographic](/rest-api-reference/visuals/7b7104f1-d277-4993-ad3a-e5883c4b776d.md)  | `/video-template/7b7104f1-d277-4993-ad3a-e5883c4b776d` | Image  |
| [Top Secret Infographic](/rest-api-reference/visuals/b8707b58-a106-44af-bb12-e30507e561af.md) | `/video-template/b8707b58-a106-44af-bb12-e30507e561af` | Image  |

#### Historical

| Template                                                                                               | ID                                                     | Output |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ------ |
| [Egyptian Hieroglyph Infographic](/rest-api-reference/visuals/a7b0d128-8478-4b34-9647-a0778b6517d0.md) | `/video-template/a7b0d128-8478-4b34-9647-a0778b6517d0` | Image  |
| [Cave Painting Infographic](/rest-api-reference/visuals/82ee75b6-597b-43a8-86bc-e4395e7c9c44.md)       | `/video-template/82ee75b6-597b-43a8-86bc-e4395e7c9c44` | Image  |

***

### Don't See a Template for Your Use Case?

Submit a support ticket and describe the type of visual you need. To submit a ticket, click the orange circle button inside Blotato.

Include the following in your request:

* A description of the visual format you need (e.g., "listicle carousel with numbered items")
* The platform you plan to post on (e.g., Instagram, TikTok, LinkedIn)
* A link to an example of the format, if available

***

### See Also

* [Create Visual API Reference](#api-api-reference-create-video)
* [Get Visual Status API Reference](#api-api-reference-find-video)
* [Media Requirements](/rest-api-reference/publish-post/media.md)

***

## Create Visual /v2/videos/from-templates

### Migrating from the Old API Format

If you are getting this error: `body.textToImageModel must be object, body.imageToVideoModel must NOT be valid`

Your workflow is using an outdated request format. The old `POST /v2/videos/creations` endpoint with `textToImageModel` and `imageToVideoModel` as string fields no longer works.

Switch to the template system using `POST /v2/videos/from-templates`:

1. Browse available templates at [my.blotato.com/videos/new](https://my.blotato.com/videos/new)
2. In n8n or Make, replace the old node with the Blotato "Create Visual" node
3. Select your template from the dropdown (start with a carousel — it renders in seconds)
4. Open the [Logs](https://my.blotato.com/logs) to inspect the exact JSON payload each template expects
5. Override inputs one by one to customize

For AI story videos, choose the template named "AI Video with AI Voice."

See also: [n8n FAQ — textToImageModel and imageToVideoModel](/integrations-and-automation-templates/n8n/faqs.md#im-using-an-older-template-with-texttoimagemodel-and-imagetovideomodel-parameters-do-these-still-work)

***

### Creating a Visual

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/videos/from-templates`

**Method:** `POST`

#### Description

This endpoint creates a new visual (image or video) from a template. Templates define the structure and input parameters for generating visuals like slideshows, quote cards, tweet cards, and more.

You can provide input parameters manually, or use the optional `prompt` parameter to have AI automatically fill in the template inputs based on your description.

Brand Kit is opt-in. Send a nonempty `prompt` and `useBrandKit: true` to include brand context when generating template inputs. An omitted or null `brandId` uses the primary brand. Without these conditions, include the required brand instructions in your prompt. MCP does not expose the same brand fields. See [Brand Kit](/settings/brand-kit.md).

#### Request

**Request Body**

| Field         | Type             | Required | Description                                                                                                                                                                                                                             |
| ------------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `templateId`  | `string`         | ✅        | The ID of the template to use. Get available templates from the `/v2/videos/templates` endpoint.                                                                                                                                        |
| `inputs`      | `object`         | ✅        | Template-specific input parameters. Structure depends on the selected template. Can be an empty object `{}` if using the `prompt` parameter.                                                                                            |
| `isDraft`     | `boolean`        | ❌        | Save as draft without rendering. Default: `false`.                                                                                                                                                                                      |
| `prompt`      | `string`         | ❌        | Optional natural language prompt to auto-fill template inputs using AI. When provided, AI interprets your description and fills in the `inputs` automatically. Any manually provided `inputs` take precedence over AI-generated values. |
| `render`      | `boolean`        | ❌        | Whether to render the visual immediately. Default: `true`.                                                                                                                                                                              |
| `title`       | `string`         | ❌        | A human-readable title for the generated video.                                                                                                                                                                                         |
| `useBrandKit` | `boolean`        | ❌        | Default: false. With a nonempty prompt, includes compiled brand context during template input generation. Requires a configured brand. See [Brand Kit](/settings/brand-kit.md).                                                         |
| `brandId`     | `string \| null` | ❌        | Brand ID for prompt-based generation with useBrandKit enabled. Omitted or null selects the primary brand.                                                                                                                               |

#### Getting Available Templates

To list all available templates and their input specifications:

```
GET https://backend.blotato.com/v2/videos/templates?fields=id,name,description,inputs
```

The template list includes premade Blotato visual templates only. User-generated visual templates created or edited in the Blotato web app are not available in n8n, Make.com, MCP, or the `/v2/videos/templates` endpoint. Use a premade Blotato visual template from the dropdown or templates endpoint.

**Query Parameters:**

| Field    | Type     | Description                                                                                                                    |
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `fields` | `string` | Comma-separated list of fields to include. Use `id,name,description,inputs` to get full template details.                      |
| `search` | `string` | Case-insensitive text search in template name or description. Characters are matched literally. Ignored when `id` is supplied. |
| `id`     | `string` | Optional template ID to get a specific template.                                                                               |

#### Inspect inputs without generating a visual

Template lookup is read-only. Do not execute a blank Create Visual request to learn the schema. Generation is a separate operation which consumes credits.

1. Call `blotato_list_visual_templates` with `{"search":"AI Video with AI Voice"}`, or send the REST lookup below.
2. Match the result to the requested output.
3. Retain its complete `id`.
4. Read its `inputs` specification for field names, nested types, defaults, allowed values, and limits.
5. Build the creation request only after confirming those inputs and the user's authorization to generate.

```http
GET /v2/videos/templates?search=AI%20Video%20with%20AI%20Voice&fields=id,name,description,inputs HTTP/1.1
Host: backend.blotato.com
blotato-api-key: YOUR_API_KEY
```

Search matches text literally, without case sensitivity. For an exact lookup, pass the returned `id` instead. An empty `items` array means no matching template was returned. Do not guess another ID or submit an empty `templateId`.

MCP supplies `id`, `description`, and `inputs`. Do not depend on a `title` or `name` field in its result. REST with `fields=id,name,description,inputs` includes the template's `name`.

These templates use different input structures:

| Template                                                                                                    | Content inputs                                               |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| [AI Video with AI Voice](/rest-api-reference/visuals/5903fe43-514d-40ee-a060-0d6628c5f8fd.md)               | `scenes`, each with `script` and `mediaSource`               |
| [Image Slideshow with Text Overlays](/rest-api-reference/visuals/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f.md)   | `slides`, each with `imageSource` and optional `textOverlay` |
| [Instagram Carousel Slideshow](/rest-api-reference/visuals/53cfec04-2500-41cf-8cc1-ba670d2c341a.md)         | `slidePrompts`, where each entry is an image prompt string   |
| [Quote Card with Monocolor Background](/rest-api-reference/visuals/77f65d2b-48cc-4adb-bfbb-5bc86f8c01bd.md) | `title` and `quotes`, where each quote is a string           |

Changing `templateId` also requires checking the new template's input schema. `slides`, `slidePrompts`, and `scenes` are not interchangeable.

For an error such as `scenes.0: missing object`, compare the submitted value at the reported path with the selected template's specification. Increasing wait time does not fix an input-validation error.

#### Responses

**Success Response**

**Status Code:** `201 Created`

Visual creation is scheduled on the queue. To check status, poll the [Get Visual Status](#api-api-reference-find-video) endpoint.

**Response Body:**

```json
{
  "item": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "queueing"
  }
}
```

**Error Responses**

**Not Found**

**Status Code:** `404 Not Found`

```json
{
  "message": "Unknown template ID"
}
```

**Too Many Requests**

Visual creation has a user-level rate limit of 30 requests / minute.

**Status Code:** `429 Too many requests`

```json
{
  "statusCode": 429,
  "message": "Rate limit exceeded, retry in 49 seconds"
}
```

#### Examples

**1. Create a Visual Using AI Prompt (Recommended)**

The easiest way to create visuals is using the `prompt` parameter. AI will interpret your description and fill in the template inputs automatically.

```http
POST https://backend.blotato.com/v2/videos/from-templates HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "templateId": "/base/v2/image-slideshow/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f/v1",
  "inputs": {},
  "prompt": "Create a 5-slide carousel about productivity tips for remote workers. Use a modern, professional style with blue tones.",
  "render": true
}
```

**2. Create a Visual with Manual Inputs**

You can also specify inputs manually for full control:

```http
POST https://backend.blotato.com/v2/videos/from-templates HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "templateId": "/base/v2/image-slideshow/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f/v1",
  "inputs": {
    "slides": [
      {
        "imageSource": "https://example.com/image1.jpg",
        "textOverlay": "Slide 1: Introduction"
      },
      {
        "imageSource": "A serene mountain landscape at sunset",
        "textOverlay": "Slide 2: AI-generated image"
      }
    ],
    "textPosition": "center",
    "aiImageModel": "replicate/recraft-ai/recraft-v3"
  },
  "render": true
}
```

**3. Combine Prompt with Manual Overrides**

You can use `prompt` for most inputs while manually specifying certain values:

```http
POST https://backend.blotato.com/v2/videos/from-templates HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "templateId": "/base/v2/image-slideshow/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f/v1",
  "inputs": {
    "textPosition": "bottom",
    "textColor": "#FFFFFF"
  },
  "prompt": "Create a 3-slide motivational carousel about morning routines",
  "render": true
}
```

In this example, AI fills in the slides content, but `textPosition` and `textColor` use your manual values.

#### Available Templates

Browse all templates with parameters and examples in the [Visual Templates](#api-visuals-README) catalog. You can also use the `/v2/videos/templates` endpoint to get the current list programmatically. Common templates include:

| Template Type                | Description                                      |
| ---------------------------- | ------------------------------------------------ |
| Image Slideshow              | Create slideshows from images with text overlays |
| Instagram Carousel Slideshow | AI-generated image carousels from text prompts   |
| Quote Card                   | Generate quote cards with stylized backgrounds   |
| Tweet Card                   | Create visual cards from tweet-style content     |
| Tutorial Carousel            | Step-by-step tutorial visuals                    |
| AI Story Video               | AI-generated story videos with narration         |
| Combine Clips                | Merge multiple video clips                       |

#### Template Input Types

Templates use various input types:

| Type      | Description                   | Example                             |
| --------- | ----------------------------- | ----------------------------------- |
| `text`    | Plain text string             | `"Hello world"`                     |
| `number`  | Numeric value                 | `42`                                |
| `boolean` | True/false                    | `true`                              |
| `enum`    | Choice from predefined values | `"top"` \| `"center"` \| `"bottom"` |
| `image`   | Image URL                     | `"https://example.com/image.jpg"`   |
| `video`   | Video URL                     | `"https://example.com/video.mp4"`   |
| `color`   | Hex color code                | `"#FF5733"`                         |
| `array`   | List of items                 | `[{...}, {...}]`                    |
| `object`  | Nested object                 | `{ "key": "value" }`                |

#### Troubleshooting

AI video renders take up to 10-15 minutes. A long render is normal and does not mean the request failed. Keep polling until the status reads `done` or the response includes `error`. If `error` is set, generation has stopped and the status will not change again, even if `status` is not a final status. If your AI agent reports the render as stuck or failing without an `error`, nudge it to check the status again before retrying.

If you're still having trouble generating a visual, navigate to `https://my.blotato.com/videos/<YOUR_VIDEO_ID>` to view and manually edit it.

#### Polling for Status

After creating a visual, poll the [Get Visual Status](#api-api-reference-find-video) endpoint to check its status:

```
GET https://backend.blotato.com/v2/videos/creations/<VIDEO_ID>
```

Status values (in order):

* `queueing` - Waiting to be processed
* `generating-script` - AI is generating the script
* `script-ready` - Script is ready, generating media
* `generating-media` - Media is being generated
* `media-ready` - Media is ready, exporting
* `exporting` - Final export in progress
* `done` - Complete. Use `mediaUrl` or `imageUrls` from the response.
* `creation-from-template-failed` - Generation failed

See [Get Visual Status](#api-api-reference-find-video) for full response details.

***

## Get Visual Status /v2/videos/creations/:id

### Check Visual Creation Status

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/videos/creations/:id`

**Method:** `GET`

#### Description

Poll this endpoint to check the status of a visual creation. After submitting a request with [Create Visual](#api-api-reference-create-video), use the returned `id` to track its progress. If the response includes `error`, generation has stopped and the status will not change again, even if `status` is not a final status.

#### Request

**Path Parameters**

| Field | Type     | Required | Description                                   |
| ----- | -------- | -------- | --------------------------------------------- |
| `id`  | `string` | Yes      | The video/visual ID returned by Create Visual |

#### Response

**Status Code:** `200 OK`

**Status Values**

| Status                          | Description                                            |
| ------------------------------- | ------------------------------------------------------ |
| `queueing`                      | Request is queued. Keep polling.                       |
| `generating-script`             | AI is generating the script. Keep polling.             |
| `script-ready`                  | Script is ready, generating media. Keep polling.       |
| `generating-media`              | Media is being generated. Keep polling.                |
| `media-ready`                   | Media is ready, exporting. Keep polling.               |
| `exporting`                     | Final export in progress. Keep polling.                |
| `done`                          | Complete. `mediaUrl` and/or `imageUrls` are available. |
| `creation-from-template-failed` | Creation failed.                                       |

If `error` is set, stop polling and report the error. For example, a visual can stay at `script-ready` with an `error` when AI media generation failed before export.

**Response: Done (success)**

```json
{
  "item": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "done",
    "createdAt": "2025-03-10T15:30:00Z",
    "mediaUrl": "https://database.blotato.io/user_1/media/video.mp4",
    "imageUrls": ["https://database.blotato.io/user_1/media/slide1.jpg", "https://database.blotato.io/user_1/media/slide2.jpg"]
  }
}
```

* `mediaUrl`: URL of the rendered video. Use this in `mediaUrls` when publishing.
* `imageUrls`: Array of image URLs (for slideshows/carousels). Use these in `mediaUrls` when publishing.

**Response: In Progress**

```json
{
  "item": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "generating-media",
    "createdAt": "2025-03-10T15:30:00Z",
    "mediaUrl": null,
    "imageUrls": null
  }
}
```

**Response: Failed**

```json
{
  "item": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "creation-from-template-failed",
    "createdAt": "2025-03-10T15:30:00Z",
    "mediaUrl": null,
    "imageUrls": null
  }
}
```

**Response: Stopped With Error**

```json
{
  "item": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "script-ready",
    "createdAt": "2025-03-10T15:30:00Z",
    "mediaUrl": null,
    "imageUrls": null,
    "error": "2/5 media failed to generate. The video has been saved as a draft. View the video here: https://my.blotato.com/videos/a1b2c3d4-e5f6-7890-abcd-ef1234567890 to retry generating the media.",
    "errorMetadata": {
      "numDraftOverlays": 2,
      "numAiOverlays": 5
    }
  }
}
```

#### Polling Pattern

```
1. Create visual: POST /videos/from-templates -> get item.id
2. Poll: GET /videos/creations/{id}
3. If item.error is set: stop and report the error
4. If item.status = "done": use item.mediaUrl or item.imageUrls
5. If item.status = "creation-from-template-failed" or "insufficient-credits": stop and report it
6. If item.status = "draft": report the draft, not a completed render
7. Otherwise: wait at least 15 seconds, go to step 2
```

#### How Long It Takes

Most visuals finish within a few minutes. AI video renders take up to 10-15 minutes. A long render is normal and does not mean the request failed. Keep polling until the status reads `done` or the response includes `error`. Treat a visual as stopped when `error` is set, even if `status` is still `script-ready`. If the wait exceeds your execution window, save the visual ID and report its current status. Check the existing visual before submitting another create request. Creating another visual starts another job and uses credits.

#### Example

```http
GET https://backend.blotato.com/v2/videos/creations/a1b2c3d4-e5f6-7890-abcd-ef1234567890 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

#### Error Response

**Status Code:** `404 Not Found`

```json
{
  "statusCode": 404,
  "message": "Not found"
}
```

***

## Delete Video /v2/videos/:id

### Deleting a single video

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/videos/:id`

**Method:** `DELETE`

#### Description

Delete the video. Useful for cleaning up old, unused videos.

#### Request

**Request Params**

The request path parameters must contain the following fields:

| Field | Type     | Required | Description |
| ----- | -------- | -------- | ----------- |
| `id`  | `string` | ✅        | Video ID    |

#### Responses

**Success Response**

**Status Code:** `204 No Content`

The video has been deleted successfully.

**Response Body:**

```
{
  "id": "VIDEO ID",
}
```

**Error Responses**

**Status Code:** `500 Internal server error`

```
{
  "statusCode": 500,
  "message": "Something went wrong"
}
```

***

## Scheduled Posts /v2/schedules

### Managing Scheduled Posts

Scheduled posts are created via [Publish Post](#api-api-reference-publish-post) using `scheduledTime` or `useNextFreeSlot`. These endpoints let you list, inspect, update, and delete scheduled posts before they publish.

To manage the recurring time slots themselves (the calendar grid), see [Weekly posting schedule](#api-api-reference-schedule-slots).

***

### List Scheduled Posts

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/schedules`

**Method:** `GET`

#### Description

Returns all scheduled posts for the current user. Only future posts are returned, ordered by scheduled time (ascending). Supports cursor-based pagination.

The web app does not have a built-in calendar filter for one brand, account, or page. To build a custom filtered calendar, list scheduled posts with this endpoint, then filter the returned `items` by `account.id`, `account.subaccountId`, `account.subId`, or `account.subaccountName`.

#### Query Parameters

| Field    | Type      | Required | Description                                             |
| -------- | --------- | -------- | ------------------------------------------------------- |
| `limit`  | `integer` | No       | Number of items per page. Min: 1, Max: 50. Default: 20. |
| `cursor` | `string`  | No       | Pagination cursor from a previous response.             |

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "sch_abc123",
      "scheduledAt": "2026-04-01T14:00:00.000Z",
      "account": {
        "id": "98432",
        "name": "Jane Smith",
        "username": "janesmith",
        "profileImageUrl": "https://...",
        "subaccountId": null,
        "subId": null,
        "subaccountName": null
      },
      "draft": {
        "accountId": "98432",
        "content": {
          "text": "Scheduled post content",
          "mediaUrls": [],
          "platform": "twitter"
        },
        "target": {
          "targetType": "twitter"
        }
      }
    }
  ],
  "count": "12",
  "cursor": "eyJzY2hlZHVsZWRBd..."
}
```

| Field                 | Type             | Description                                                                                                               |
| --------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `items`               | `array`          | List of scheduled posts                                                                                                   |
| `items[].id`          | `string`         | Schedule ID. Use this for get, update, and delete operations.                                                             |
| `items[].scheduledAt` | `string`         | ISO 8601 UTC timestamp for when the post will publish.                                                                    |
| `items[].account`     | `object or null` | The target social account. Null if the account was disconnected.                                                          |
| `items[].draft`       | `object`         | The post payload. Same structure as the `post` object in [Publish Post](#api-api-reference-publish-post).                 |
| `count`               | `string`         | Total number of future scheduled posts.                                                                                   |
| `cursor`              | `string`         | Pagination cursor. Pass this as the `cursor` query parameter to fetch the next page. Absent when there are no more pages. |

#### Example

```http
GET https://backend.blotato.com/v2/schedules?limit=10 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

With pagination:

```http
GET https://backend.blotato.com/v2/schedules?limit=10&cursor=eyJzY2hlZHVsZWRBd... HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

### Get Scheduled Post

#### Endpoint

**URL:** `/schedules/:id`

**Method:** `GET`

#### Description

Returns a single scheduled post by ID.

#### Path Parameters

| Field | Type     | Required | Description                                         |
| ----- | -------- | -------- | --------------------------------------------------- |
| `id`  | `string` | Yes      | Schedule ID from the List Scheduled Posts endpoint. |

#### Response

**Status Code:** `200 OK`

```json
{
  "schedule": {
    "id": "sch_abc123",
    "scheduledAt": "2026-04-01T14:00:00.000Z",
    "account": {
      "id": "98432",
      "name": "Jane Smith",
      "username": "janesmith",
      "profileImageUrl": "https://...",
      "subaccountId": null,
      "subId": null,
      "subaccountName": null
    },
    "draft": {
      "accountId": "98432",
      "content": {
        "text": "Scheduled post content",
        "mediaUrls": [],
        "platform": "twitter"
      },
      "target": {
        "targetType": "twitter"
      }
    }
  }
}
```

Returns `404` if the schedule does not exist or belongs to another user.

***

### Update Scheduled Post

#### Endpoint

**URL:** `/schedules/:id`

**Method:** `PATCH`

#### Description

Update a scheduled post's content, scheduled time, or both. At least one field is required. The scheduled time must be in the future.

When the scheduled time changes, the publishing job is re-queued for the new time.

#### Path Parameters

| Field | Type     | Required | Description                                         |
| ----- | -------- | -------- | --------------------------------------------------- |
| `id`  | `string` | Yes      | Schedule ID from the List Scheduled Posts endpoint. |

#### Request Body

```json
{
  "patch": {
    "scheduledTime": "2026-04-05T10:00:00Z",
    "draft": {
      "accountId": "98432",
      "content": {
        "text": "Updated post content",
        "mediaUrls": [],
        "platform": "twitter"
      },
      "target": {
        "targetType": "twitter"
      }
    }
  }
}
```

| Field                 | Type     | Required | Description                                                                                                                                                                             |
| --------------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `patch.scheduledTime` | `string` | No       | New ISO 8601 timestamp. Must be in the future.                                                                                                                                          |
| `patch.draft`         | `object` | No       | Updated post payload. Same structure as the `post` object in [Publish Post](#api-api-reference-publish-post). When provided, send the full object -- partial updates are not supported. |

#### Response

**Status Code:** `204 No Content`

#### Errors

| Status | Reason                                                      |
| ------ | ----------------------------------------------------------- |
| `404`  | Schedule not found                                          |
| `422`  | Empty patch, invalid date, or scheduled time is in the past |

#### Examples

**Reschedule to a new time**

```http
PATCH https://backend.blotato.com/v2/schedules/sch_abc123 HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "patch": {
    "scheduledTime": "2026-04-05T10:00:00Z"
  }
}
```

**Update the post text**

```http
PATCH https://backend.blotato.com/v2/schedules/sch_abc123 HTTP/1.1
Content-Type: application/json
blotato-api-key: YOUR_API_KEY

{
  "patch": {
    "draft": {
      "accountId": "98432",
      "content": {
        "text": "New text for this scheduled post",
        "mediaUrls": [],
        "platform": "twitter"
      },
      "target": {
        "targetType": "twitter"
      }
    }
  }
}
```

**Reschedule to the next available slot**

The update endpoint does not accept `useNextFreeSlot`. To move a post to the next available slot, first call [Find Next Available Slot](#api-api-reference-schedule-slots--find-next-available-slot), then pass the returned time as `scheduledTime`:

```
1. POST /v2/schedule/slots/next-available
   Body: { "platform": "twitter", "accountId": "98432" }
   Response: { "slot": { "slotId": "slot_1", "slotTime": "2026-04-02T09:00:00Z" } }

2. PATCH /v2/schedules/sch_abc123
   Body: { "patch": { "scheduledTime": "2026-04-02T09:00:00Z" } }
```

***

### Delete Scheduled Post

#### Endpoint

**URL:** `/schedules/:id`

**Method:** `DELETE`

#### Description

Delete a scheduled post and cancel its publishing job. This action cannot be undone.

#### Path Parameters

| Field | Type     | Required | Description                                         |
| ----- | -------- | -------- | --------------------------------------------------- |
| `id`  | `string` | Yes      | Schedule ID from the List Scheduled Posts endpoint. |

#### Response

**Status Code:** `204 No Content`

#### Errors

| Status | Reason                                                                                                |
| ------ | ----------------------------------------------------------------------------------------------------- |
| `404`  | The schedule was not found, does not belong to your account, or is no longer a future scheduled post. |

#### Example

```http
DELETE https://backend.blotato.com/v2/schedules/sch_abc123 HTTP/1.1
blotato-api-key: YOUR_API_KEY
```

***

## Schedule Slots

Schedule slots define the recurring time windows in your content calendar. When you publish a post with `useNextFreeSlot: true`, Blotato picks the next open slot matching the target platform and queues the post at that time.

To manage the posts queued in those slots, see [Schedules](#api-api-reference-schedules).

### How Slots and Schedules Work Together

1. **Slots** define your posting cadence -- for example, "every Monday at 9:00 AM UTC for Twitter."
2. When you publish a post with `useNextFreeSlot: true` (see [Publish Post](#api-api-reference-publish-post)), Blotato finds the next slot that matches the target platform and account, and schedules the post at that time.
3. A slot is "occupied" when a scheduled post is already queued at that time. The next call to `useNextFreeSlot` skips occupied slots and picks the next open one.
4. You manage what is queued (the **schedules**) and when things are queued (the **slots**) separately.

***

### List Slots

#### Endpoint

**Base URL:** `https://backend.blotato.com/v2`

**URL:** `/schedule/slots`

**Method:** `GET`

#### Description

Returns all scheduling slots for the current user.

#### Response

**Status Code:** `200 OK`

```json
{
  "items": [
    {
      "id": "slot_1",
      "hour": 9,
      "minute": 0,
      "day": "monday",
      "selectedTargets": [
        {
          "platform": "twitter",
          "accountId": "98432",
          "subaccountId": null
        }
      ]
    },
    {
      "id": "slot_2",
      "hour": 14,
      "minute": 30,
      "day": "wednesday",
      "selectedTargets": [
        {
          "platform": "instagram",
          "accountId": "98434",
          "subaccountId": null
        },
        {
          "platform": "linkedin",
          "accountId": "98435",
          "subaccountId": null
        }
      ]
    }
  ]
}
```

| Field                                    | Type             | Description                                                                               |
| ---------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------- |
| `items[].id`                             | `string`         | Slot ID                                                                                   |
| `items[].hour`                           | `integer`        | Hour in UTC (0-23)                                                                        |
| `items[].minute`                         | `integer`        | Minute (0-59)                                                                             |
| `items[].day`                            | `string`         | Day of week: `monday`, `tuesday`, `wednesday`, `thursday`, `friday`, `saturday`, `sunday` |
| `items[].selectedTargets`                | `array`          | Platforms and accounts this slot applies to                                               |
| `items[].selectedTargets[].platform`     | `string`         | Platform name                                                                             |
| `items[].selectedTargets[].accountId`    | `string or null` | Account ID. Null means the slot applies to all accounts on that platform.                 |
| `items[].selectedTargets[].subaccountId` | `string or null` | Subaccount ID (for Facebook Pages / LinkedIn Company Pages).                              |

***

### Create Slots

#### Endpoint

**URL:** `/schedule/slots`

**Method:** `POST`

#### Description

Create one or more scheduling slots. Each slot defines a day, time (in UTC), and which platforms/accounts it applies to. Returns an error if a slot at the same day and time already exists.

#### Request Body

```json
{
  "slots": [
    {
      "hour": 9,
      "minute": 0,
      "day": "monday",
      "selectedTargets": [
        {
          "platform": "twitter",
          "accountId": "98432",
          "subaccountId": null
        }
      ]
    }
  ]
}
```

| Field                     | Type      | Required | Description                                                                                                 |
| ------------------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `slots`                   | `array`   | Yes      | Array of slots to create                                                                                    |
| `slots[].hour`            | `integer` | Yes      | Hour in UTC (0-23)                                                                                          |
| `slots[].minute`          | `integer` | Yes      | Minute (0-59)                                                                                               |
| `slots[].day`             | `string`  | Yes      | Day of week (e.g., `monday`)                                                                                |
| `slots[].selectedTargets` | `array`   | Yes      | Platforms and accounts. Set `accountId` to `null` for a slot that applies to all accounts on that platform. |

#### Response

**Status Code:** `201 Created`

Returns the created slots with their generated IDs.

***

### Update Slot Targets

#### Endpoint

**URL:** `/schedule/slots/:id`

**Method:** `PATCH`

#### Description

Replace the selected targets for a slot. Send the full list of targets -- this is a full replacement, not a partial update.

#### Path Parameters

| Field | Type     | Required | Description |
| ----- | -------- | -------- | ----------- |
| `id`  | `string` | Yes      | Slot ID     |

#### Request Body

```json
{
  "patch": {
    "selectedTargets": [
      {
        "platform": "twitter",
        "accountId": "98432",
        "subaccountId": null
      },
      {
        "platform": "instagram",
        "accountId": "98434",
        "subaccountId": null
      }
    ]
  }
}
```

#### Response

**Status Code:** `204 No Content`

***

### Delete Slot

#### Endpoint

**URL:** `/schedules/slots/:id`

**Method:** `DELETE`

#### Description

Delete a scheduling slot. If future posts still reference this slot, deletion fails. Do not cancel queued posts automatically. Ask whether to keep the slot, reschedule the posts, or cancel them. Obtain authorization before changing or deleting the affected schedules. Changing a schedule's time clears its slot association, so an authorized reschedule preserves the post while freeing the slot. See [Schedules](#api-api-reference-schedules).

Past scheduled posts linked to this slot are cleaned up automatically.

#### Path Parameters

| Field | Type     | Required | Description |
| ----- | -------- | -------- | ----------- |
| `id`  | `string` | Yes      | Slot ID     |

#### Response

**Status Code:** `204 No Content`

#### Errors

| Status | Reason                                                                                                            |
| ------ | ----------------------------------------------------------------------------------------------------------------- |
| `400`  | Slot has future scheduled content. Preserve the posts until the user chooses to keep, reschedule, or cancel them. |

***

### Find Next Available Slot

#### Endpoint

**URL:** `/schedule/slots/next-available`

**Method:** `POST`

#### Description

Find the next available (unoccupied) slot for a given platform and account. Returns the slot ID and the UTC time of the next open slot.

Use this when you want to reschedule a post to the next free slot via the [Update Schedule](#api-api-reference-schedules--update-scheduled-post) endpoint, since that endpoint accepts `scheduledTime` but not `useNextFreeSlot`.

#### Request Body

```json
{
  "platform": "twitter",
  "accountId": "98432",
  "subaccountId": null
}
```

| Field          | Type     | Required | Description                                                       |
| -------------- | -------- | -------- | ----------------------------------------------------------------- |
| `platform`     | `string` | Yes      | Platform name                                                     |
| `accountId`    | `string` | No       | Account ID. Omit to match slots for all accounts on the platform. |
| `subaccountId` | `string` | No       | Subaccount ID for Facebook Pages / LinkedIn Company Pages.        |

#### Response

**Status Code:** `201 Created`

```json
{
  "slot": {
    "slotId": "slot_1",
    "slotTime": "2026-04-02T09:00:00Z"
  }
}
```

| Field           | Type     | Description                                                |
| --------------- | -------- | ---------------------------------------------------------- |
| `slot.slotId`   | `string` | The slot that was matched                                  |
| `slot.slotTime` | `string` | ISO 8601 UTC time of the next open occurrence of this slot |

Returns `400` if no slots are configured for the given platform/account.

***

## Instagram: API and MCP publishing

Instagram publishing requires an image or video. Use a connected Instagram account from account lookup. A caption alone is insufficient.

### Fields

| Field           | Use                                                                                        |
| --------------- | ------------------------------------------------------------------------------------------ |
| `mediaType`     | Optional `reel` or `story`. Choose the format requested by the user.                       |
| `collaborators` | Optional array of Instagram usernames without `@`.                                         |
| `altText`       | Optional image description.                                                                |
| `coverImageUrl` | Optional public Reel cover URL.                                                            |
| `shareToFeed`   | Optional boolean for also sharing a Reel to the main feed.                                 |
| `audioName`     | Optional name for the Reel's original audio.                                               |
| `trial`         | Optional trial Reel settings. Read the publish schema before setting `graduationStrategy`. |
| `firstComment`  | Optional first comment after publishing. Stories do not support it.                        |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Use a single image, a video, or multiple media URLs for a carousel. For a Reel, check the export guidance and automatic-conversion limits. Videos longer than 120 seconds must already satisfy requirements if cropping or conversion would otherwise be needed.

Read [Instagram media requirements](/rest-api-reference/publish-post/media.md#instagram) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "instagram",
  "text": "A caption for your Reel.",
  "mediaUrls": [
    "https://example.com/video.mp4"
  ],
  "mediaType": "reel"
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "instagram",
      "text": "A caption for your Reel.",
      "mediaUrls": [
        "https://example.com/video.mp4"
      ]
    },
    "target": {
      "targetType": "instagram",
      "mediaType": "reel"
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

`shareToFeed` affects Reels. It does not populate the comments inbox or change image posts. Comments, DMs, and analytics have their own sync behavior.

See [Instagram account guide](/social-accounts-and-platform-faqs/instagram.md), [platform help](/social-accounts-and-platform-faqs/instagram/faqs.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## Facebook: API and MCP publishing

Choose a connected Facebook Page. Publishing to personal Facebook profiles is not supported. Get `pageId` from the account's subaccounts.

### Fields

| Field          | Use                                                                 |
| -------------- | ------------------------------------------------------------------- |
| `pageId`       | Required Facebook Page ID.                                          |
| `mediaType`    | Optional `reel` or `story` for the media format.                    |
| `link`         | Optional URL for a link preview.                                    |
| `firstComment` | Optional first comment after publishing. Stories do not support it. |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Publish text or image posts to the Page feed. Videos publish as Reels or Stories. A Story requires media. See the Facebook media section for duration and conversion requirements.

Read [Facebook media requirements](/rest-api-reference/publish-post/media.md#facebook) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "facebook",
  "text": "A post for your Facebook Page.",
  "mediaUrls": [],
  "pageId": "PAGE_ID_FROM_LOOKUP"
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "facebook",
      "text": "A post for your Facebook Page.",
      "mediaUrls": []
    },
    "target": {
      "targetType": "facebook",
      "pageId": "PAGE_ID_FROM_LOOKUP"
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

Select the Page requested by the user when the account has several Pages. A connected personal Facebook account is the authorization account, rather than the posting destination.

See [Facebook account guide](/social-accounts-and-platform-faqs/facebook.md), [platform help](/social-accounts-and-platform-faqs/facebook/faqs.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## TikTok: API and MCP publishing

TikTok requires media, a privacy setting, and 6 boolean declarations. Set these from the user's intended privacy, interaction, brand, and AI-content choices. The example uses `SELF_ONLY`.

### Fields

| Field                                            | Use                                                                                             |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| `privacyLevel`                                   | Required: `SELF_ONLY`, `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, or `FOLLOWER_OF_CREATOR`. |
| `disabledComments, disabledDuet, disabledStitch` | Required booleans for interaction settings.                                                     |
| `isBrandedContent, isYourBrand, isAiGenerated`   | Required booleans describing the content.                                                       |
| `title`                                          | Optional image-post title.                                                                      |
| `autoAddMusic`                                   | Optional music setting for image posts.                                                         |
| `isDraft`                                        | Optional draft submission instead of direct publication.                                        |
| `imageCoverIndex, videoCoverTimestamp`           | Optional cover selection. Read the schema for units and valid values.                           |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Use one video or an image post. Do not put multiple videos in one post. Check media dimensions and conversion requirements before publishing.

Read [TikTok media requirements](/rest-api-reference/publish-post/media.md#tiktok) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "tiktok",
  "text": "A caption for your TikTok video.",
  "mediaUrls": [
    "https://example.com/video.mp4"
  ],
  "privacyLevel": "SELF_ONLY",
  "disabledComments": false,
  "disabledDuet": false,
  "disabledStitch": false,
  "isBrandedContent": false,
  "isYourBrand": false,
  "isAiGenerated": false
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "tiktok",
      "text": "A caption for your TikTok video.",
      "mediaUrls": [
        "https://example.com/video.mp4"
      ]
    },
    "target": {
      "targetType": "tiktok",
      "privacyLevel": "SELF_ONLY",
      "disabledComments": false,
      "disabledDuet": false,
      "disabledStitch": false,
      "isBrandedContent": false,
      "isYourBrand": false,
      "isAiGenerated": false
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

Starter permits posting to 3 distinct TikTok accounts during a rolling 24-hour window. This counts posting destinations, rather than connections. A TikTok draft still needs completion in TikTok. Report draft delivery separately from a public post.

See [TikTok account guide](/social-accounts-and-platform-faqs/tiktok.md), [platform help](/social-accounts-and-platform-faqs/tiktok/faqs.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## YouTube: API and MCP publishing

Use a connected YouTube account and a video URL. Provide `title`, `privacyStatus`, and `shouldNotifySubscribers`. The example uses an unlisted video.

### Fields

| Field                     | Use                                                                            |
| ------------------------- | ------------------------------------------------------------------------------ |
| `title`                   | Required title, up to 100 characters. Do not include `<` or `>`.               |
| `privacyStatus`           | Required: `private`, `public`, or `unlisted`.                                  |
| `shouldNotifySubscribers` | Required boolean.                                                              |
| `isMadeForKids`           | Optional audience declaration.                                                 |
| `containsSyntheticMedia`  | Optional synthetic-media declaration.                                          |
| `thumbnailUrl`            | Optional public custom-thumbnail URL. Account verification requirements apply. |
| `playlistIds`             | Optional array of up to 5 playlist IDs from subaccounts.                       |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Use a video URL in `mediaUrls`. The `text` becomes the description. This publish operation does not create channel banners, profile pictures, or YouTube community image posts.

Read [YouTube media requirements](/rest-api-reference/publish-post/media.md#youtube) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "youtube",
  "text": "Your video description.",
  "mediaUrls": [
    "https://example.com/video.mp4"
  ],
  "title": "Your video title",
  "privacyStatus": "unlisted",
  "shouldNotifySubscribers": false
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "youtube",
      "text": "Your video description.",
      "mediaUrls": [
        "https://example.com/video.mp4"
      ]
    },
    "target": {
      "targetType": "youtube",
      "title": "Your video title",
      "privacyStatus": "unlisted",
      "shouldNotifySubscribers": false
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

For playlist selection, fetch the account's subaccounts. Custom thumbnails require the supported account verification state. A scheduled Blotato post and an unlisted YouTube video are different outcomes.

See [YouTube account guide](/social-accounts-and-platform-faqs/youtube.md), [platform help](/social-accounts-and-platform-faqs/youtube/faqs.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## LinkedIn: API and MCP publishing

Use the connected LinkedIn `accountId`. Omit `pageId` for the personal profile. For a company Page, choose `pageId` from the account's subaccounts.

### Fields

| Field    | Use                                                               |
| -------- | ----------------------------------------------------------------- |
| `pageId` | Optional LinkedIn company Page ID. Omit for the personal profile. |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Publish text, a single image, a video, or an image carousel. For carousels, Blotato builds a LinkedIn Document from the image URLs. Read the media requirements before submitting a video.

Read [LinkedIn media requirements](/rest-api-reference/publish-post/media.md#linkedin) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "linkedin",
  "text": "A post for your LinkedIn personal profile.",
  "mediaUrls": []
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "linkedin",
      "text": "A post for your LinkedIn personal profile.",
      "mediaUrls": []
    },
    "target": {
      "targetType": "linkedin"
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

Do not add a company Page ID unless the user selects the Page. The example below publishes to the personal profile. LinkedIn analytics availability differs from publishing support. See the analytics guide.

See [LinkedIn account guide](/social-accounts-and-platform-faqs/linkedin.md), [platform help](/social-accounts-and-platform-faqs/linkedin/faqs.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## Pinterest: API and MCP publishing

Call `blotato_list_pinterest_boards` with the Pinterest `accountId`. Use the selected board's `id` as `boardId`. REST lookup: `GET /v2/social/pinterest/boards?accountId=...`.

### Fields

| Field     | Use                                               |
| --------- | ------------------------------------------------- |
| `boardId` | Required board ID from the board lookup.          |
| `title`   | Optional title, up to 100 characters.             |
| `altText` | Optional image description, up to 500 characters. |
| `link`    | Optional destination URL, up to 2,048 characters. |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Use an image, a video, or 2–5 images for a carousel. A video Pin contains one video. The publisher rejects more than 5 media items.

Read [Pinterest media requirements](/rest-api-reference/publish-post/media.md#pinterest) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "pinterest",
  "text": "A description for your Pin.",
  "mediaUrls": [
    "https://example.com/image.jpg"
  ],
  "boardId": "BOARD_ID_FROM_LOOKUP"
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "pinterest",
      "text": "A description for your Pin.",
      "mediaUrls": [
        "https://example.com/image.jpg"
      ]
    },
    "target": {
      "targetType": "pinterest",
      "boardId": "BOARD_ID_FROM_LOOKUP"
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

Pinterest account eligibility and connection requirements apply before publishing. Read the connection troubleshooting page if the account is blocked from API publishing.

See [Pinterest account guide](/social-accounts-and-platform-faqs/bluesky-1.md), [platform help](/social-accounts-and-platform-faqs/bluesky-1/connect-accounts-1.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## X (Twitter): API and MCP publishing

Use `twitter` as the platform value, including when the user calls the service X. No extra target identifier is required after selecting `accountId`.

### Fields

| Field             | Use                                                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `additionalPosts` | Optional thread posts, each with `text` and `mediaUrls`. For REST, put this array inside `post.content`, outside `post.target`. |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Publish text, images, or a video. For a thread, use the main post plus `additionalPosts`. Validate each post's media and text rather than treating the thread as one caption.

Read [X (Twitter) media requirements](/rest-api-reference/publish-post/media.md#twitter) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "twitter",
  "text": "A post for X.",
  "mediaUrls": []
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "twitter",
      "text": "A post for X.",
      "mediaUrls": []
    },
    "target": {
      "targetType": "twitter"
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

Account-specific text limits and platform errors still apply. Follow the X FAQ for account questions and the returned validation message for a rejected post.

See [X (Twitter) account guide](/social-accounts-and-platform-faqs/faqs.md), [platform help](/social-accounts-and-platform-faqs/faqs.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## Threads: API and MCP publishing

Use `threads` as the platform value. No extra target identifier is required after selecting `accountId`.

### Fields

| Field             | Use                                                                                              |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| `replyControl`    | Optional: `everyone`, `accounts_you_follow`, or `mentioned_only`.                                |
| `additionalPosts` | Optional thread posts, each with `text` and `mediaUrls`. For REST, place this in `post.content`. |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Each post accepts text, one image, or one video. Multiple media URLs in a single Threads post fail. To share several images, create a thread with one image per post using `additionalPosts`.

Read [Threads media requirements](/rest-api-reference/publish-post/media.md#threads) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "threads",
  "text": "A post for Threads.",
  "mediaUrls": []
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "threads",
      "text": "A post for Threads.",
      "mediaUrls": []
    },
    "target": {
      "targetType": "threads"
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

A Threads thread and a media carousel are different structures. Use one media item per post even if a generic media schema allows larger arrays.

See [Threads account guide](/social-accounts-and-platform-faqs/threads.md), [platform help](/social-accounts-and-platform-faqs/threads/connect-accounts.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## Bluesky: API and MCP publishing

Use `bluesky` as the platform value. No extra target identifier is required after selecting `accountId`.

### Fields

| Field             | Use                                                                                              |
| ----------------- | ------------------------------------------------------------------------------------------------ |
| `additionalPosts` | Optional thread posts, each with `text` and `mediaUrls`. For REST, place this in `post.content`. |

For MCP, pass platform-specific fields alongside `accountId`, `platform`, `text`, and `mediaUrls`. For REST, platform-specific target fields belong inside `post.target`. Thread content belongs inside `post.content.additionalPosts`.

### Media and formats

Publish text, up to 4 images, or one video. Blotato waits for Bluesky video processing during publication. Follow the video URL format requirements in the media reference.

Read [Bluesky media requirements](/rest-api-reference/publish-post/media.md#bluesky) and [upload and conversion rules](#api-guides-media-uploads-and-conversion). Public media URLs go directly in `mediaUrls`. No upload step required.

### MCP example

Call `blotato_create_post` with the following shape. Replace lookup IDs and example media URLs with returned IDs and your media. Adjust the example's settings to the user's request.

```json
{
  "accountId": "ACCOUNT_ID_FROM_LOOKUP",
  "platform": "bluesky",
  "text": "A post for Bluesky.",
  "mediaUrls": []
}
```

### REST example

Send this JSON to `POST https://backend.blotato.com/v2/posts` with your `blotato-api-key` header.

```json
{
  "post": {
    "accountId": "ACCOUNT_ID_FROM_LOOKUP",
    "content": {
      "platform": "bluesky",
      "text": "A post for Bluesky.",
      "mediaUrls": []
    },
    "target": {
      "targetType": "bluesky"
    }
  }
}
```

### Verify the result

1. Save the returned `postSubmissionId`.
2. If processing continues, use `blotato_get_post_status` or `GET /v2/posts/:postSubmissionId`.
3. Report the returned status and public URL when supplied.

For scheduling, add `scheduledTime` or `useNextFreeSlot` as an MCP argument or a REST top-level sibling of `post`. See [Scheduling](#api-guides-scheduling) and [completion rules](#api-guides-async-jobs-and-polling).

### Limits and troubleshooting

A video submission is not finished until publishing completes. Keep the submission ID and use the status lookup if the MCP create call returns processing status.

See [Bluesky account guide](/social-accounts-and-platform-faqs/bluesky.md), [platform help](/social-accounts-and-platform-faqs/bluesky/connect-accounts.md), and the [Publish Post reference](#api-api-reference-publish-post) for options and examples.

***

## Async Workflows

Publishing, source extraction, and visual generation use the Submit → Poll → Result pattern. Read the [completion rules](#api-guides-async-jobs-and-polling) for the exact operation. For task instructions using MCP, start with [Agent workflows](/agent-workflows/agent-workflows.md).

### System Prompt for Agents

You can use the following snippet to instruct your AI agent on how to interact with Blotato:

> **Blotato API Protocol**: All creation operations (posts, videos, sources) are **async**.
>
> 1. Call the `CREATE` endpoint. Record the ID from the 201 response (`id` for sources, `item.id` for videos, `postSubmissionId` for posts). A scheduled post also returns `scheduledTime`, the resolved UTC publish time.
> 2. Check the `GET` endpoint at least 10 seconds apart for posts and sources, and at least 15 seconds apart for visuals.
> 3. Continue polling while status is processing (see status values below).
> 4. Stop on a terminal result. Report success only for the operation's success state, not for a failure or draft.
> 5. Set a waiting limit. Preserve the operation ID if processing remains unfinished. Do not submit a duplicate write because a poll failed or time elapsed.
>
> **Terminal Status Values by Operation**:
>
> * **Sources**: `completed` (success) or `failed`
> * **Videos**: `done` (success), `creation-from-template-failed`, `insufficient-credits`, or any response with `item.error`. A `draft` is not a rendered result.
> * **Posts**: `published` (success), `failed`, or `scheduled` for a future publication. Stop immediate polling after a scheduled result.
>
> **All Video Status Values** (in order): `queueing` -> `generating-script` -> `script-ready` -> `generating-media` -> `media-ready` -> `exporting` -> `done`
>
> **Always fetch accounts first**: `GET /v2/users/me/accounts` to get `accountId` for publishing. To get `pageId` for Facebook/LinkedIn or `playlistIds` for YouTube, also fetch `GET /v2/users/me/accounts/{accountId}/subaccounts`.
>
> For social platforms, set `content.platform` and `target.targetType` to the same value (e.g., both `"twitter"`). The [REST webhook target](#api-api-reference-publish-post--webhook) uses `other` with `webhook`.
>
> **Content Calendar**: `GET /v2/schedules` to list scheduled posts. `PATCH /v2/schedules/:id` to update content or time. `DELETE /v2/schedules/:id` to cancel. `GET /v2/schedule/slots` for recurring time slots.
>
> Full reference: [API reference for agents](/start-with-an-ai-agent/llm.md).

***

### 1. Create Source -> Get Source

**Goal**: Research a topic or extract content from a URL so Blotato can generate related visuals.

**Endpoints**:

* Create: `POST /v2/source-resolutions-v3`
* Poll: `GET /v2/source-resolutions-v3/:id`

**Source Types**:

* `youtube`, `tiktok`, `article`, `pdf`, `audio`, `twitter` - Extract content from a URL (each type requires a `url` field)
* `text` - Transform raw text content with optional AI instructions
* `perplexity-query` - AI-powered web research on any topic (requires a `text` field)

```mermaid
sequenceDiagram
    participant Agent
    participant API
    Note over Agent: 1. Submit Source (URL, Text, or Query)
    Agent->>API: POST /v2/source-resolutions-v3<br/>{ source: { sourceType: "...", ... } }
    API-->>Agent: 201 Created { id: "src_123" }

    Note over Agent: 2. Poll for Extraction
    loop At least 10 seconds between checks
        Agent->>API: GET /v2/source-resolutions-v3/src_123
        API-->>Agent: { status: "processing" }
        Note over Agent: Wait...
    end

    Note over Agent: 3. Receive Result
    API-->>Agent: { status: "completed", content: "Extracted text...", title: "..." }
```

#### Example Payloads

**From YouTube URL**:

```json
{
  "source": {
    "sourceType": "youtube",
    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
  }
}
```

**From AI Research Query** (use this for agents to research topics):

```json
{
  "source": {
    "sourceType": "perplexity-query",
    "text": "latest trends in AI-generated content for social media"
  }
}
```

**From Text with Custom Instructions**:

```json
{
  "source": {
    "sourceType": "text",
    "text": "Your raw content here..."
  },
  "customInstructions": "Summarize in 5 bullet points for Instagram carousel"
}
```

***

### 2. Create Visual -> Get Visual

**Goal**: Generate a video or image from a template.

**Endpoints**:

* Create: `POST /v2/videos/from-templates`
* Poll: `GET /v2/videos/creations/:id`
* Templates: `GET /v2/videos/templates?fields=id,name,description,inputs`

#### Discovering Templates

If you don't have a specific template ID, retrieve available templates:

```http
GET https://backend.blotato.com/v2/videos/templates?fields=id,name,description,inputs&search=carousel
```

Choose the template for the requested output. An image carousel, a narrated video, and a clip compilation use different templates and input structures. Inspect [the selected template's inputs](#api-api-reference-create-video--inspect-inputs-without-generating-a-visual) before generating.

#### Visual Generation Flow

```mermaid
sequenceDiagram
    participant Agent
    participant API
    Note over Agent: 0. (Optional) Discover Templates
    Agent->>API: GET /v2/videos/templates
    API-->>Agent: List of templates with IDs

    Note over Agent: 1. Submit Generation Request
    Agent->>API: POST /v2/videos/from-templates<br/>{ templateId: "...", inputs: {...}, prompt: "..." }
    API-->>Agent: 201 Created { item: { id: "vid_456", status: "queueing" } }

    Note over Agent: 2. Poll for Rendering
    loop At least 15 seconds between checks
        Agent->>API: GET /v2/videos/creations/vid_456
        API-->>Agent: { item: { status: "generating-media" } }
        Note over Agent: Wait...
    end

    Note over Agent: 3. Receive Media URLs
    API-->>Agent: { item: { status: "done", mediaUrl: "https://...", imageUrls: [...] } }
```

#### Example Payload (with AI Prompt)

```json
{
  "templateId": "/base/v2/image-slideshow/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f/v1",
  "inputs": {},
  "prompt": "Create a 5-slide carousel about productivity tips",
  "render": true
}
```

#### Example Payload (with Manual Inputs)

Replace the example image URLs with the user's accessible media URLs before submitting. These URLs illustrate the request structure, not content to publish.

```json
{
  "templateId": "/base/v2/image-slideshow/5903b592-1255-43b4-b9ac-f8ed7cbf6a5f/v1",
  "inputs": {
    "slides": [
      {
        "imageSource": "https://example.com/slide-1.jpg",
        "textOverlay": "Introduction"
      },
      {
        "imageSource": "https://example.com/slide-2.jpg",
        "textOverlay": "Next step"
      }
    ]
  },
  "render": true
}
```

***

### 3. Create Post -> Get Post

**Goal**: Publish content to a social platform.

**Endpoints**:

* Create: `POST /v2/posts`
* Poll: `GET /v2/posts/:postSubmissionId`
* Account Lookup: `GET /v2/users/me/accounts` ([docs](#api-api-reference-accounts))
* Subaccounts (for pageId): `GET /v2/users/me/accounts/:accountId/subaccounts` ([docs](#api-api-reference-accounts--list-subaccounts-pages))

> **n8n / Make.com users**: Check the operations in your installed integration. If it exposes Get Post, pass the original `postSubmissionId`. Otherwise, use an HTTP request to `GET /v2/posts/{postSubmissionId}` with your Blotato credential. Do not assume all integration versions expose the same operations.

> \[!IMPORTANT] **Prerequisites**:
>
> 1. **`accountId`**: Get this from `GET /v2/users/me/accounts`. Always fetch the user's accounts before publishing. See [Accounts reference](#api-api-reference-accounts).
> 2. **`pageId`**: Required for Facebook Pages and LinkedIn company Pages. Omit it for a LinkedIn personal-profile post. Get it from `GET /v2/users/me/accounts/{accountId}/subaccounts`. See [How to Get the Right IDs](#api-api-reference-accounts--how-to-get-the-right-ids-for-publishing).
> 3. **`mediaUrls`**: Use existing accessible media URLs, or `mediaUrl` and `imageUrls` from an authorized visual-generation request. No upload step required for these URLs. Do not generate new media for a request to publish an existing file. Text-only posts omit media when the selected platform supports them.
> 4. **`content.platform` and `target.targetType`**: For social platforms, set both to the same value (e.g., both `"twitter"`).

#### Step 0: Fetch Available Accounts (Always Do This First)

```
GET https://backend.blotato.com/v2/users/me/accounts
```

Response:

```json
{
  "items": [
    { "id": "98432", "platform": "twitter", "fullname": "Jane Smith", "username": "janesmith" }
  ]
}
```

For a Facebook Page or LinkedIn company Page, fetch subaccounts and select the requested `pageId`. Omit `pageId` for a LinkedIn personal profile. For YouTube, fetch subaccounts only when playlist placement is requested:

```
GET https://backend.blotato.com/v2/users/me/accounts/{accountId}/subaccounts
```

See [Accounts reference](#api-api-reference-accounts) for full details.

#### Publish Flow

```mermaid
sequenceDiagram
    participant Agent
    participant API
    Note over Agent: 0. Fetch User Accounts
    Agent->>API: GET /v2/users/me/accounts
    API-->>Agent: List of accounts with IDs

    Note over Agent: 1. Submit Post
    Agent->>API: POST /v2/posts<br/>{ post: { accountId, content, target } }
    API-->>Agent: 201 Created { postSubmissionId: "sub_789" }

    Note over Agent: 2. Poll for Publishing
    loop At least 10 seconds between checks
        Agent->>API: GET /v2/posts/sub_789
        API-->>Agent: { status: "in-progress" }
        Note over Agent: Wait...
    end

    Note over Agent: 3. Success
    API-->>Agent: { status: "published", publicUrl: "https://twitter.com/..." }
```

#### Example Payload

```json
{
  "post": {
    "accountId": "98432",
    "content": {
      "text": "Hello world!",
      "mediaUrls": ["https://database.blotato.io/user_1/media/vid_456.mp4"],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  }
}
```

Get `accountId` from `GET /v2/users/me/accounts`. For social platforms, set `content.platform` and `target.targetType` to the same value.

To schedule instead of publishing immediately, add `useNextFreeSlot` or `scheduledTime` as a top-level field (sibling of `post`, not inside it). See [Publish Post](#api-api-reference-publish-post--request-body) for details.

***

### Complete End-to-End Workflow (Recommended for AI Agents)

This is the standard content creation sequence: research topic → create visual → publish to social.

```mermaid
graph TD
    A["Step 0: Fetch User Accounts<br/>GET /v2/users/me/accounts"] --> B["Step 1: Create Source<br/>POST /v2/source-resolutions-v3"]
    B --> C["Step 2: Poll Source<br/>GET /v2/source-resolutions-v3/:id<br/>Until status = completed"]
    C --> D["Step 3: Create Visual<br/>POST /v2/videos/from-templates<br/>Use source content in prompt"]
    D --> E["Step 4: Poll Visual<br/>GET /v2/videos/creations/:id<br/>Until status = done"]
    E --> F["Step 5: Create Post<br/>POST /v2/posts<br/>Use mediaUrl from step 4"]
    F --> G["Step 6: Poll Post<br/>GET /v2/posts/:postSubmissionId<br/>Until status = published"]
    G --> H["Done! Post is live"]

    style A fill:#e1f5ff
    style H fill:#c8e6c9
```

#### Pseudocode for Agents

```
1. accountsList = GET /v2/users/me/accounts
2. sourceId = POST /v2/source-resolutions-v3 (specify sourceType: youtube, article, text, etc.)
3. LOOP: source = GET /v2/source-resolutions-v3/:sourceId
   - IF status = "completed": BREAK
   - IF status = "failed": STOP, report error
   - ELSE: WAIT at least 10 seconds, then check again
4. videoId = POST /v2/videos/from-templates (use source.content in prompt)
5. LOOP: video = GET /v2/videos/creations/:videoId
   - IF item.status = "done": BREAK
   - IF `item.error` is set: STOP, report error
   - IF item.status is "creation-from-template-failed" or "insufficient-credits": STOP, report error
   - IF item.status = "draft": STOP, report that no rendered result is available
   - ELSE: WAIT at least 15 seconds, then check again
6. postSubmissionId = POST /v2/posts (accountId from step 1, mediaUrl/imageUrls from step 5)
   - For social platforms, set content.platform and target.targetType to the same value
7. LOOP: post = GET /v2/posts/:postSubmissionId
   - IF status = "published": BREAK
   - IF status = "failed": STOP, check errorMessage
   - IF status = "scheduled": STOP, report scheduledTime
   - ELSE: WAIT at least 10 seconds, then check again
8. RETURN: post.publicUrl
```

***

### 4. Managing the Content Calendar

**Goal**: View, update, reschedule, or delete scheduled posts. Manage the recurring time slots that define your posting cadence.

**How Slots and Schedules Work Together**:

1. **Slots** define recurring time windows (e.g., "Monday 9 AM for Twitter"). Create them with `POST /v2/schedule/slots`.
2. When you publish with `useNextFreeSlot: true`, Blotato finds the next open slot matching the target platform and queues the post at that time.
3. A slot is "occupied" when a scheduled post is already queued at that time. The next `useNextFreeSlot` call skips occupied slots.
4. **Schedules** are the queued posts. **Slots** are the time windows. Manage them separately.

**Endpoints**:

* Schedules: `GET /v2/schedules`, `GET /v2/schedules/:id`, `PATCH /v2/schedules/:id`, `DELETE /v2/schedules/:id` ([docs](#api-api-reference-schedules))
* Slots: `GET /v2/schedule/slots`, `POST /v2/schedule/slots`, `PATCH /v2/schedule/slots/:id`, `DELETE /v2/schedules/slots/:id`, `POST /v2/schedule/slots/next-available` ([docs](#api-api-reference-schedule-slots))

#### Schedule Response Shape

The `draft` field in a schedule is the same shape as the `post` object in [Publish Post](#api-api-reference-publish-post):

```json
{
  "id": "sch_abc123",
  "scheduledAt": "2026-04-01T14:00:00.000Z",
  "account": {
    "id": "98432",
    "name": "Jane Smith",
    "username": "janesmith",
    "profileImageUrl": "https://...",
    "subaccountId": null,
    "subId": null,
    "subaccountName": null
  },
  "draft": {
    "accountId": "98432",
    "content": {
      "text": "Scheduled post content",
      "mediaUrls": [],
      "platform": "twitter"
    },
    "target": {
      "targetType": "twitter"
    }
  }
}
```

> **Note**: The response uses `scheduledAt` for the publish time. The update endpoint accepts `scheduledTime` as the input field name.

#### Rescheduling to the Next Free Slot

The `PATCH /v2/schedules/:id` endpoint accepts `scheduledTime` but not `useNextFreeSlot`. To move a post to the next available slot:

```
1. POST /v2/schedule/slots/next-available
   Body: { "platform": "twitter", "accountId": "98432" }
   Response: { "slot": { "slotId": "slot_1", "slotTime": "2026-04-02T09:00:00Z" } }

2. PATCH /v2/schedules/sch_abc123
   Body: { "patch": { "scheduledTime": "2026-04-02T09:00:00Z" } }
```

#### Pseudocode for Calendar Management

```
1. schedules = GET /v2/schedules (paginate with cursor if needed)
2. Display scheduled posts to user (scheduledAt, account info, draft content)
3. To reschedule: PATCH /v2/schedules/:id { patch: { scheduledTime: "new ISO 8601" } }
4. To update content: PATCH /v2/schedules/:id { patch: { draft: { full post object } } }
5. To delete: DELETE /v2/schedules/:id
6. To reschedule to next free slot:
   a. nextSlot = POST /v2/schedule/slots/next-available { platform, accountId }
   b. PATCH /v2/schedules/:id { patch: { scheduledTime: nextSlot.slot.slotTime } }
```

***

### Error Handling

All asynchronous operations can fail during processing. Handle failures gracefully:

#### Status Failure

When polling returns `status: "failed"`, check the error message:

**Source Failure** (Get Source endpoint):

```json
{
  "status": "failed",
  "message": "Unable to extract content from URL"
}
```

**Video Failure** (Get Visual Status endpoint):

```json
{
  "item": {
    "status": "script-ready",
    "error": "2/5 media failed to generate. The video has been saved as a draft."
  }
}
```

**Post Failure** (Get Post Status endpoint):

```json
{
  "postSubmissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "failed",
  "errorMessage": "Invalid account credentials"
}
```

#### Retry Strategy

* **Don't retry automatically on failure** - most failures are permanent
* **Log the error message** and report it to the user
* **Inform user** that the operation failed and provide next steps
* Retry failed reads after a delay. For a create request with an uncertain result, check existing work before resubmitting. For `429`, wait and reduce request frequency. See [Errors and retries](#api-guides-error-handling).

***

### Platform-Specific Setup

Different platforms require different fields and have different requirements. See detailed guides:

* [**Instagram Setup**](/settings/social-accounts/instagram.md) - Reels, Stories, Collaborators, Alt Text
* [**LinkedIn Setup**](/settings/social-accounts/linkedin.md) - Company Pages, Professional Network
* [**Facebook Setup**](/settings/social-accounts/facebook.md) - Page ID, Media Types
* [**Platform Requirements**](/tips-and-tricks/social-platform-requirements.md) - All platforms at a glance

When publishing, always include the required fields for the target platform:

* **Facebook**: `target.pageId` (required), `target.mediaType` -- required `"reel"` for videos (regular feed videos no longer supported), optional `"story"` for Stories, omit for text/image posts
* **LinkedIn**: `target.pageId` (optional)
* **Pinterest**: `target.boardId` (required)
* **YouTube**: `target.playlistIds` (optional - array of playlist IDs from subaccounts)
* **TikTok**: `target.privacyLevel`, `target.disabledComments`, etc. (required)
* **Instagram**: `target.mediaType` (optional - default is "reel")
* **Twitter, Threads, Bluesky**: Minimal required fields

## Machine-readable sources

* [This reference as raw Markdown](https://help.blotato.com/start-with-an-ai-agent/llm.md)
* [REST OpenAPI schemas](https://backend.blotato.com/openapi.json)
* [Documentation index](https://help.blotato.com/llms.txt)

Use the connected MCP schemas for tool calls and OpenAPI for REST requests. Individual tutorial pages are not required to follow the workflows in this reference.


---

# 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 dynamically 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.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 `build a script that syncs our docs to a CMS` 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.
