Draft, schedule and publish social posts to X, LinkedIn, Instagram, TikTok, YouTube and more
Maintain this server? Claim it
Call this server's tools from your browser. Nothing to install.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
Creates a draft post with everything the composer saves: caption, title, description, hashtags, media, the accounts it goes to, per-account text (account_overrides, channel_overrides), per-account media and covers, and each platform's settings. Media can be public URLs (downloaded and stored server-side) or media keys from upload_media. Use this only once you have the full brief from the user: the post type, the text, and the media if the type needs it. `type` is required and is never guessed: use the type the user stated (text, image, video or blog); if they did not say, ask them before calling this tool. A blog post goes to WordPress: title is required and becomes the page heading. Put the body in description, as HTML; it is sent exactly as written. Plain text also works: blank lines become paragraphs. WordPress keeps p, br, hr, h2 to h6 (with id), ul, ol, li, blockquote, strong, em, a (href, rel, target, title), code, pre, abbr, time, figure, figcaption, the table tags and img (src, alt, width, height). Do not use h1 (the title is already the page heading), script or style (WordPress removes the tag but leaves its text visible). The first image is the featured image; any others are added under the body in order. Images only, no video. Then schedule_post puts the draft on the calendar. To schedule straight away, call schedule_post with the content instead: it creates and schedules in one call. Pass the same idempotency_key on a retry so a network error never makes two drafts. Returns the draft (post) plus warnings[] naming any platform whose caption limit the content exceeds, and created=false when the idempotency_key matched an existing draft. Does NOT schedule or publish anything.
Required. The type the user asked for. Ask if they have not said. blog is a WordPress blog post.
Title, used by platforms that have one (YouTube, Pinterest). Required for a blog post, where it becomes the WordPress page heading.
tiktok settings keyed by tiktok account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.
bluesky settings keyed by bluesky account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.
The caption / body text.
facebook settings keyed by facebook account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.
Without the # is fine.
media_key of a cover image for video posts.
pinterest settings keyed by pinterest account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.
wordpress settings keyed by wordpress account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.
media_key values from upload_media.
Public http(s) URLs of the ORIGINAL files to download and attach, up to 100 MB each. Never a re-encoded copy.
Where this post goes: each account's ref from list_accounts (e.g. "x:acme_studio"), or its id. Prefer the ref; it is what the person reads when approving. Always the complete list, unlike the per-account maps: an account left out is removed from the post, with its text and settings.
Any unique string; a repeat with the same key returns the same draft.
One account's own video cover, keyed by account ref or id: a media_key, or null to use the post's cover.
One account's own media, keyed by account ref or id: its whole ordered list of media_key values; [] posts no media there, null goes back to the post's media.
Text for one account, keyed by account ref or id: title, caption, hashtags. Send only the accounts you are changing; every other account keeps its text. An entry is that account's whole text (a field it leaves out is cleared); null clears the account's text. Wins over channel_overrides and the base text.
Text for every account of one platform, keyed by platform id (e.g. "instagram"): "the Instagram caption". Send only the platforms you are changing; null clears the text of that platform's accounts. An account_overrides entry for the same account wins field by field.
The video frame, in milliseconds, used as the cover when no cover image is set.
This tool isn't marked read-only, so it may change something on the server or in an account you connected.
Creates a draft post with everything the composer saves: caption, title, description, hashtags, media, the accounts it goes to, per-account text (account_overrides, channel_overrides), per-account media and covers, and each platform's settings. Media can be public URLs (downloaded and stored server-side) or media keys from upload_media. Use this only once you have the full brief from the user: the post type, the text, and the media if the type needs it. `type` is required and is never guessed: use the type the user stated (text, image, video or blog); if they did not say, ask them before calling this tool. A blog post goes to WordPress: title is required and becomes the page heading. Put the body in description, as HTML; it is sent exactly as written. Plain text also works: blank lines become paragraphs. WordPress keeps p, br, hr, h2 to h6 (with id), ul, ol, li, blockquote, strong, em, a (href, rel, target, title), code, pre, abbr, time, figure, figcaption, the table tags and img (src, alt, width, height). Do not use h1 (the title is already the page heading), script or style (WordPress removes the tag but leaves its text visible). The first image is the featured image; any others are added under the body in order. Images only, no video. Then schedule_post puts the draft on the calendar. To schedule straight away, call schedule_post with the content instead: it creates and schedules in one call. Pass the same idempotency_key on a retry so a network error never makes two drafts. Returns the draft (post) plus warnings[] naming any platform whose caption limit the content exceeds, and created=false when the idempotency_key matched an existing draft. Does NOT schedule or publish anything.
| Argument | Type | Description |
|---|---|---|
| type* | string | Required. The type the user asked for. Ask if they have not said. blog is a WordPress blog post. |
| title | string | Title, used by platforms that have one (YouTube, Pinterest). Required for a blog post, where it becomes the WordPress page heading. |
| tiktok | object | tiktok settings keyed by tiktok account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| bluesky | object | bluesky settings keyed by bluesky account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| content | string | The caption / body text. |
| object | facebook settings keyed by facebook account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. | |
| hashtags | array | Without the # is fine. |
| cover_key | string | media_key of a cover image for video posts. |
| object | pinterest settings keyed by pinterest account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. | |
| wordpress | object | wordpress settings keyed by wordpress account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| media_keys | array | media_key values from upload_media. |
| media_urls | array | Public http(s) URLs of the ORIGINAL files to download and attach, up to 100 MB each. Never a re-encoded copy. |
| account_ids | array | Where this post goes: each account's ref from list_accounts (e.g. "x:acme_studio"), or its id. Prefer the ref; it is what the person reads when approving. Always the complete list, unlike the per-account maps: an account left out is removed from the post, with its text and settings. |
| description | string | |
| idempotency_key | string | Any unique string; a repeat with the same key returns the same draft. |
| cover_by_account | object | One account's own video cover, keyed by account ref or id: a media_key, or null to use the post's cover. |
| media_by_account | object | One account's own media, keyed by account ref or id: its whole ordered list of media_key values; [] posts no media there, null goes back to the post's media. |
| account_overrides | object | Text for one account, keyed by account ref or id: title, caption, hashtags. Send only the accounts you are changing; every other account keeps its text. An entry is that account's whole text (a field it leaves out is cleared); null clears the account's text. Wins over channel_overrides and the base text. |
| channel_overrides | object | Text for every account of one platform, keyed by platform id (e.g. "instagram"): "the Instagram caption". Send only the platforms you are changing; null clears the text of that platform's accounts. An account_overrides entry for the same account wins field by field. |
| cover_timestamp_ms | integer | The video frame, in milliseconds, used as the cover when no cover image is set. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"type"
],
"properties": {
"type": {
"enum": [
"text",
"image",
"video",
"blog"
],
"type": "string",
"description": "Required. The type the user asked for. Ask if they have not said. blog is a WordPress blog post."
},
"title": {
"type": "string",
"maxLength": 200,
"description": "Title, used by platforms that have one (YouTube, Pinterest). Required for a blog post, where it becomes the WordPress page heading."
},
"tiktok": {
"type": "object",
"description": "tiktok settings keyed by tiktok account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"properties": {
"delivery": {
"enum": [
"direct_post"
],
"type": "string",
"description": "[optional] direct_post publishes the post to the account; it is the only value. Default direct_post."
},
"interactions": {
"type": "object",
"required": [
"comment",
"duet",
"stitch"
],
"properties": {
"duet": {
"type": "boolean"
},
"stitch": {
"type": "boolean"
},
"comment": {
"type": "boolean"
}
},
"description": "[account default] Allow comments, duets and stitches on this post. Default: the switches saved on the account. Anything the creator turned off in TikTok stays off. Send only when the user asks."
},
"brand_content": {
"type": "boolean",
"description": "[optional] Branded content for someone else. Needs commercial_content; cannot be SELF_ONLY. Default false."
},
"brand_organic": {
"type": "boolean",
"description": "[optional] Promotes the creator's own brand. Needs commercial_content. Default false."
},
"privacy_level": {
"enum": [
"PUBLIC_TO_EVERYONE",
"MUTUAL_FOLLOW_FRIENDS",
"FOLLOWER_OF_CREATOR",
"SELF_ONLY"
],
"type": "string",
"description": "[optional] Who can see the post. SELF_ONLY when the user says \"only me\". Default PUBLIC_TO_EVERYONE."
},
"commercial_content": {
"type": "boolean",
"description": "[optional] The post promotes a brand, product or service. Default false."
}
}
}
},
"bluesky": {
"type": "object",
"description": "bluesky settings keyed by bluesky account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"alt_text",
"labels"
],
"properties": {
"labels": {
"type": "array",
"items": {
"enum": [
"sexual",
"nudity",
"porn",
"graphic-media"
],
"type": "string"
},
"description": "[optional] Content warnings: at most one of sexual (Suggestive), nudity, porn (Adult), plus graphic-media. Only on posts with images or video. Default []."
},
"alt_text": {
"type": "array",
"items": {
"type": "string"
},
"description": "[optional] One description per file, in the order this account publishes its media; \"\" leaves a file undescribed. Exactly as many entries as files. Up to 2000 characters each. Default: no alt text."
}
}
}
},
"content": {
"type": "string",
"maxLength": 100000,
"description": "The caption / body text."
},
"facebook": {
"type": "object",
"description": "facebook settings keyed by facebook account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"video_as"
],
"properties": {
"video_as": {
"type": "string",
"const": "reel",
"description": "[optional] Publish a video as a Reel: 3 to 90 seconds, vertical 9:16, at most 30 Reels per Page per day. Leave the account out for a classic Page video (the default)."
}
}
}
},
"hashtags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Without the # is fine."
},
"cover_key": {
"type": "string",
"description": "media_key of a cover image for video posts."
},
"pinterest": {
"type": "object",
"description": "pinterest settings keyed by pinterest account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"board_id"
],
"properties": {
"link": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "[optional] The Pin's clickable destination. A URL in the caption is never clickable on Pinterest. Default none."
},
"alt_text": {
"type": "string",
"maxLength": 500,
"description": "[optional] Describes the image for screen readers. Default none; the title is never used."
},
"board_id": {
"type": "string",
"pattern": "^\\d+$",
"description": "[account default] The board the Pin goes on. Default: the account's saved board, and no account has one yet, so every Pin needs this: ask the user which board, from list_options req_type list_board."
}
}
}
},
"wordpress": {
"type": "object",
"description": "wordpress settings keyed by wordpress account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"properties": {
"seo": {
"type": "object",
"properties": {
"title": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"minLength": 1
},
"focus_keyphrase": {
"type": "string",
"minLength": 1
}
},
"description": "[optional] Yoast SEO fields. Only for an account whose list_accounts entry shows seo \"yoast\". Default: Yoast's own."
},
"slug": {
"type": "string",
"maxLength": 200,
"minLength": 1,
"description": "[optional] The URL slug. Default: made from the title."
},
"tags": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "[optional] Tag names; a new one is created. Only for a post type that has tags. Default none."
},
"terms": {
"type": "object",
"description": "[optional] Terms of the post type's own taxonomies, as { taxonomy slug: [names] }, e.g. { \"genre\": [\"Romance\"] }. list_post_type names each type's taxonomies. Names must already exist on the site; an unknown one is refused at schedule with the site's list. Default none.",
"propertyNames": {
"type": "string",
"pattern": "^[a-z0-9_-]{1,32}$"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1
}
},
"status": {
"enum": [
"publish",
"draft"
],
"type": "string",
"description": "[optional] publish, or draft to review it in wp-admin. Default publish."
},
"excerpt": {
"type": "string",
"minLength": 1,
"description": "[optional] Short summary for feeds and link previews. Default: WordPress makes one from the body."
},
"post_type": {
"type": "string",
"pattern": "^[a-z0-9_-]{1,20}$",
"description": "[optional] The post type to publish as, e.g. a short story instead of a blog post: a slug from list_options req_type list_post_type. Default post."
},
"categories": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "[optional] Category names that already exist on the site; an unknown one is refused at schedule with the site's list. Only for a post type that has categories. Default: the site's default category."
},
"featured_image_alt": {
"type": "string",
"minLength": 1,
"description": "[optional] Alt text for the featured image (the first image). Default none."
}
}
}
},
"media_keys": {
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20,
"description": "media_key values from upload_media."
},
"media_urls": {
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20,
"description": "Public http(s) URLs of the ORIGINAL files to download and attach, up to 100 MB each. Never a re-encoded copy."
},
"account_ids": {
"type": "array",
"items": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"maxItems": 10,
"minItems": 1,
"description": "Where this post goes: each account's ref from list_accounts (e.g. \"x:acme_studio\"), or its id. Prefer the ref; it is what the person reads when approving. Always the complete list, unlike the per-account maps: an account left out is removed from the post, with its text and settings."
},
"description": {
"type": "string"
},
"idempotency_key": {
"type": "string",
"maxLength": 200,
"description": "Any unique string; a repeat with the same key returns the same draft."
},
"cover_by_account": {
"type": "object",
"description": "One account's own video cover, keyed by account ref or id: a media_key, or null to use the post's cover.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "string",
"maxLength": 512
},
{
"type": "null"
}
]
}
},
"media_by_account": {
"type": "object",
"description": "One account's own media, keyed by account ref or id: its whole ordered list of media_key values; [] posts no media there, null goes back to the post's media.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20
},
{
"type": "null"
}
]
}
},
"account_overrides": {
"type": "object",
"description": "Text for one account, keyed by account ref or id: title, caption, hashtags. Send only the accounts you are changing; every other account keeps its text. An entry is that account's whole text (a field it leaves out is cleared); null clears the account's text. Wins over channel_overrides and the base text.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 10000
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"maxItems": 30
},
"description": {
"type": "string",
"description": "This account's caption. \"\" posts no text on this account."
}
}
},
{
"type": "null"
}
]
}
},
"channel_overrides": {
"type": "object",
"description": "Text for every account of one platform, keyed by platform id (e.g. \"instagram\"): \"the Instagram caption\". Send only the platforms you are changing; null clears the text of that platform's accounts. An account_overrides entry for the same account wins field by field.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 10000
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"maxItems": 30
},
"description": {
"type": "string",
"description": "This account's caption. \"\" posts no text on this account."
}
}
},
{
"type": "null"
}
]
}
},
"cover_timestamp_ms": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "The video frame, in milliseconds, used as the cover when no cover image is set."
}
}
}Reserves storage for one file and returns a presigned upload_url (valid 1 day) plus the media_key it will be stored under. Images up to 20 MB, videos up to 200 MB. Use this in Claude Code, a terminal or a script, for a local file or any video: PUT the ORIGINAL file bytes, unmodified, to upload_url with the returned content-type header and exactly `bytes` bytes, then pass media_key to create_post or update_post. Never re-encode, resize or compress the file first. In a chat app that shows cards (Claude.ai, Claude Desktop, Claude mobile) call upload_file instead: the user picks the file and nothing has to be allowed. The PUT goes to the storage host in upload_url (<account>.r2.cloudflarestorage.com). A code sandbox that blocks it needs that host allowed: On Claude.ai a Pro or Max user allows it in Settings → Capabilities; on a Team or Enterprise plan the organization owner adds it in Admin settings → Capabilities → Additional allowed domains. Returns upload_url, media_key, headers and expires_in. Does NOT upload anything by itself, and does NOT accept a URL or base64; use upload_media for those.
| Argument | Type | Description |
|---|---|---|
| bytes* | integer | Exact size of the file in bytes. The URL only accepts a body of this length. |
| filename* | string | The file name with its extension, e.g. clip.mp4. |
| content_type* | string | MIME type of the file, e.g. video/mp4 or image/png. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"filename",
"content_type",
"bytes"
],
"properties": {
"bytes": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Exact size of the file in bytes. The URL only accepts a body of this length.",
"exclusiveMinimum": 0
},
"filename": {
"type": "string",
"maxLength": 200,
"minLength": 1,
"description": "The file name with its extension, e.g. clip.mp4."
},
"content_type": {
"type": "string",
"description": "MIME type of the file, e.g. video/mp4 or image/png."
}
}
}Permanently deletes a draft post and its target rows. Use this only when the user explicitly asks to discard a draft. Returns deleted=true, the post id and what the deleted draft was (title, caption, type, media count). Does NOT delete scheduled or published posts (returns an error), and does NOT remove anything already published on a platform.
| Argument | Type | Description |
|---|---|---|
| post_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"post_id"
],
"properties": {
"post_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
}
}Reports who this connection is, the one workspace it acts on (use switch_workspace to change it), the plan, billing state, entitlements, quotas, limits and a plain-language list of what is blocked right now. Use this to see what the connection can do, and after an error mentions a plan or quota. Returns user, credential, workspace, plan, billing, entitlements, quotas, limits and blocked[]. The plan and quotas.subscription belong to the subscription of the workspace owner (ownedBy says whether that is the user) and are counted across every workspace that owner has; quotas.workspace is this workspace alone. Say which is which when reporting numbers. Does NOT change anything. Works on every plan, including a workspace with no plan.
No arguments.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {}
}Returns the full publishing rules for one platform or all of them: caption and title limits and units, media counts, sizes, formats, whether media is required, hashtags, mentions, native scheduling, plus the global file rules. Use this before drafting to answer "will this fit on X / Instagram / TikTok". Returns platforms[] (one object per platform, with effectiveBodyLimit) and global. Does NOT look at a specific account; list_accounts carries the per-account effective limits.
| Argument | Type | Description |
|---|---|---|
| platform | string | Platform id such as "x", "linkedin", "tiktok". Omit for every platform. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"platform": {
"type": "string",
"description": "Platform id such as \"x\", \"linkedin\", \"tiktok\". Omit for every platform."
}
}
}Fetches one post with its per-account rows: status, publish time, published time and any error per account. Use this after schedule_post to confirm what happened, or when the user asks why a post failed. Returns the post (content, media, metadata, socialAccountIds) and targets[] with platform and username. Refer to posts by title or caption and to accounts by platform and username when talking to the user; ids are for tool calls. Does NOT retry or change the post.
| Argument | Type | Description |
|---|---|---|
| post_id* | string | The post id from list_posts or create_post. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"post_id"
],
"properties": {
"post_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "The post id from list_posts or create_post."
}
}
}Lists the social accounts connected to the workspace, with health, how many posts are queued on each, and the effective limits for that account. Use this to pick the accounts for create_post and schedule_post, or to check whether an account needs reconnecting. Returns accounts[] (id, ref, platform, username, health, scheduledCount, limits, blockedByPlan, and seo for WordPress accounts), nextCursor and hasMore. seo is "yoast" when the WordPress site lets this account write Yoast SEO fields (schedule_post wordpress.seo), and "core" when it does not. health.status is the same as the dashboard: an expired access token that renews itself is still active; only needs_reconnect means the user must reconnect. ref is the account as "platform:handle" (e.g. "x:acme_studio"): pass it wherever a tool takes an account, since it is what the person reads when approving; it is null when the account has no handle, and then the id is used. Does NOT connect or disconnect accounts; that happens in the dashboard.
| Argument | Type | Description |
|---|---|---|
| sort | string | createdAt (default), username or platform. |
| limit | integer | Page size, 1–100. |
| cursor | string | Opaque cursor from the previous page. Omit for the first page. |
| groupId | string | Only accounts in this channel group. |
| platform | string | Filter by platform id, e.g. "x", "instagram". |
Lists channel groups (named sets of accounts) with their member account ids. Use this when the user names a group ("post to my brand accounts") to resolve it to the account ids schedule_post takes. Returns groups[] (id, name, accountIds, accountCount), nextCursor and hasMore. Does NOT create or edit groups.
| Argument | Type | Description |
|---|---|---|
| name | string | Filter by exact group name. |
| sort | string | createdAt (default) or name. |
| limit | integer | Page size, 1–100. |
| cursor | string | Opaque cursor from the previous page. Omit for the first page. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"name": {
"type": "string",
"description": "Filter by exact group name."
},
"sort": {
"enum": [
"createdAt",
"name"
],
"type": "string",
"description": "createdAt (default) or name."
},
"limit": {
"type": "integer",
"default": 20,
"maximum": 100,
"minimum": 1,
"description": "Page size, 1–100."
},
"cursor": {
"type": "string",
"description": "Opaque cursor from the previous page. Omit for the first page."
}
}
}Lists the choices one account offers for a setting the schedule needs, read live from the platform. Requests: list_board (pinterest, ids go in pinterest.board_id); list_post_type (wordpress, ids go in wordpress.post_type). Use this when the user posts to Pinterest: list the boards, ask which one, and pass its id as board_id. Use list_post_type when the user wants a WordPress post published as something other than a blog post (a short story, a page): pass the matching id as post_type; the name lists the taxonomies that type takes in terms. Returns field (where the ids go) and choices[] with id and name. Refer to a choice by its name when talking to the user. Does NOT change anything.
| Argument | Type | Description |
|---|---|---|
| platform* | string | That account's platform id from list_accounts, e.g. "pinterest". |
| req_type* | string | What to list. |
| account_id* | string | An account id from list_accounts. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"account_id",
"platform",
"req_type"
],
"properties": {
"platform": {
"type": "string",
"description": "That account's platform id from list_accounts, e.g. \"pinterest\"."
},
"req_type": {
"enum": [
"list_board",
"list_post_type"
],
"type": "string",
"description": "What to list."
},
"account_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "An account id from list_accounts."
}
}
}Lists posts in the workspace: drafts, scheduled, published and failed. Use this to find drafts to schedule, to review the queue, or to find a post id for get_post, update_post or delete_post. Returns posts[] (id, status, type, title, content, mediaUrls, scheduledFor), nextCursor and hasMore. Refer to posts by title or caption when talking to the user; ids are for tool calls. Does NOT include per-account results; call get_post for those.
| Argument | Type | Description |
|---|---|---|
| to | string | ISO 8601 upper bound on the sort field. |
| from | string | ISO 8601 lower bound on the sort field. |
| sort | string | createdAt (default) or scheduledFor (only posts that have one). |
| limit | integer | Page size, 1–100. |
| order | string | |
| cursor | string | Opaque cursor from the previous page. Omit for the first page. |
| status | string | draft, scheduled, published or failed. |
| accountId | string | Only posts targeting this account. |
Lists every workspace the user belongs to, marks the one this connection acts on, and says what each allows through the connector: none (Free or no plan) or full (Solo, Growth, Pro). Use this when the user names another workspace or a tool reports a plan lock; then switch_workspace moves the connection. Returns workspaces[] (id, name, slug, isOwner, current, plan, access). Does NOT switch, create or change any workspace.
No arguments.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {}
}Schedules a post to one or more accounts at publish_at (ISO 8601 UTC), or publishes immediately with publish_now=true. This publishes to real accounts. One call does it all: without post_id it creates the post from the content and settings given and schedules it; with post_id it schedules that draft, applying any fields sent first. It takes everything the composer can set: caption, title, media, accounts, per-account text (account_overrides, channel_overrides), per-account media and covers, per-account times, and each platform's settings. With post_id, the per-account maps change only the accounts they name, as in update_post: every other account keeps what the draft has. account_ids is the complete list: an account left out is removed. Each platform setting says whether it is required, an account default or optional; a Pinterest account needs pinterest.board_id, from list_options. Every problem on every account is refused in one error, before anything is armed. Use this only after the user has confirmed the content, the accounts and the time. A refused call writes nothing: no draft is created and no draft is changed. Pass the same idempotency_key on a retry without post_id so a network error never makes two posts; with post_id a retry is safe as is. Returns success, post_id and results[] with one row per account (platform, username, status, error). Call get_post afterwards to show exactly what was scheduled. Does NOT publish now unless publish_now is true.
| Argument | Type | Description |
|---|---|---|
| type | string | Required without post_id: the type the user asked for. Ask if they have not said. blog is a WordPress blog post. |
| title | string | Title, used by platforms that have one (YouTube, Pinterest). Required for a blog post, where it becomes the WordPress page heading. |
| tiktok | object |
A connection acts on one workspace at a time. This moves it onto another workspace the user belongs to (id or slug from list_workspaces) for every later call, until switched again, and returns the new context. Use this when the user names a workspace other than the current one; confirm with them before switching. Returns switched and the full get_context shape for the new workspace, which says what that workspace's plan allows. Does NOT create workspaces and does NOT touch posts or accounts.
| Argument | Type | Description |
|---|---|---|
| workspace* | string | Workspace id or slug from list_workspaces. |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"workspace"
],
"properties": {
"workspace": {
"type": "string",
"minLength": 1,
"description": "Workspace id or slug from list_workspaces."
}
}
}Edits a draft or a scheduled post, the same edit the composer makes: caption, title, description, hashtags, type, media keys, cover, accounts, per-account text, media and covers, platform settings, and the time. Only the fields passed change; media_keys: null clears the media. The per-account maps (account_overrides, channel_overrides, media_by_account, cover_by_account, each platform's settings) change only the accounts they name: send just the accounts you are changing, and every other account keeps its text, media and settings. An account's entry is its whole text or settings; account_overrides: { id: null } clears that account's text. account_ids is the complete list: an account left out is removed from the post. A scheduled post is checked the way schedule_post checks one, and nothing is written when it is refused. It can no longer be edited from 10 minutes before its first account fires, or once an account has started preparing (an Instagram or Pinterest video prepares earlier). publish_at on a scheduled post moves it; on a draft it is the time the draft keeps. Use this when the user revises a post before it goes out. Returns the updated post and warnings[] for any platform caption limit the new content exceeds. Does NOT edit published or failed posts (returns an error), and does NOT ingest URLs; use upload_media first.
| Argument | Type | Description |
|---|---|---|
| type | string | Post type. Keep the type the draft already has unless the user asked to change it. |
| title | string | Title, used by platforms that have one (YouTube, Pinterest). Required for a blog post, where it becomes the WordPress page heading. |
| tiktok | object | tiktok settings keyed by tiktok account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
Called only by the JuzPost upload card, never by the model. Reserves storage for one file and starts a multipart upload. Returns media_key, upload_id, part_size and parts.
| Argument | Type | Description |
|---|---|---|
| bytes* | integer | |
| filename* | string | |
| content_type* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"filename",
"content_type",
"bytes"
],
"properties": {
"bytes": {
"type": "integer",
"maximum": 9007199254740991,
"exclusiveMinimum": 0
},
"filename": {
"type": "string",
"maxLength": 200,
"minLength": 1
},
"content_type": {
"type": "string"
}
}
}Shows an upload card where the user picks images or videos from their device (images up to 20 MB, videos up to 200 MB); the card stores the original files in the workspace. Use this in a chat app that shows cards (Claude.ai, Claude Desktop, Claude mobile) whenever the user attached a file in the chat or wants to add one from their device: you cannot read their attachment, so let them pick it here. When they finish, the card adds each file name and media_key to your context and asks the user to send a message; pass those media_key values to create_post or update_post. Returns nothing to attach by itself. Does NOT work in Claude Code or a terminal (use create_upload_url there) and does NOT take a link (use upload_media).
No arguments.
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {}
}Called only by the JuzPost upload card, never by the model. Completes an upload once every part is stored. Returns media_key, bytes and content_type.
| Argument | Type | Description |
|---|---|---|
| media_key* | string | |
| upload_id* | string |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"media_key",
"upload_id"
],
"properties": {
"media_key": {
"type": "string"
},
"upload_id": {
"type": "string"
}
}
}Stores the original image or video in the workspace from a file the chat app hands over, a public URL (up to 100 MB) or inline base64 (up to 3 MB), checking the real file type and the storage quota. Use this when the user gives you a link to attach to a post, or when the chat app hands you their attachment as `file` (ChatGPT does); then pass the returned media_key to create_post or update_post. `file` is filled by the chat app with an https download link, never by you: a path you see such as /mnt/user-data/uploads/… is on the user's side and cannot be read here. For a file the user attached or has on their device, call upload_file in a chat app that shows cards (Claude), or create_upload_url in Claude Code or a terminal. Use `base64` only with the original bytes of a small image you read from disk; never draw, reconstruct, re-encode or compress an image you have only seen. Returns media_key, bytes, content_type and source. Does NOT attach the file to any post by itself and does NOT accept private or local-network URLs.
| Argument | Type | Description |
|---|---|---|
| url | string | Public http(s) URL of the original file. Either this, file or base64. |
| file | object | Filled by the chat app (ChatGPT) with the attachment's https download_url; never a local path. Either this, url or base64. |
| base64 | string | The original file bytes as base64, read from disk (a data: URI is fine). Either this, file or url. |
| filename | string | |
| content_type | string | MIME type, e.g. image/jpeg. Required with base64. |
Called only by the JuzPost upload card, never by the model. Stores one part of a file started with upload_begin: the original bytes as base64, exactly part_size bytes except the last part. Returns part_number and bytes.
| Argument | Type | Description |
|---|---|---|
| data* | string | The part bytes as base64. |
| media_key* | string | |
| upload_id* | string | |
| part_number* | integer |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"media_key",
"upload_id",
"part_number",
"data"
],
"properties": {
"data": {
"type": "string",
"description": "The part bytes as base64."
},
"media_key": {
"type": "string"
},
"upload_id": {
"type": "string"
},
"part_number": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 1
}
}
}{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"sort": {
"enum": [
"createdAt",
"username",
"platform"
],
"type": "string",
"description": "createdAt (default), username or platform."
},
"limit": {
"type": "integer",
"default": 20,
"maximum": 100,
"minimum": 1,
"description": "Page size, 1–100."
},
"cursor": {
"type": "string",
"description": "Opaque cursor from the previous page. Omit for the first page."
},
"groupId": {
"type": "string",
"description": "Only accounts in this channel group."
},
"platform": {
"type": "string",
"description": "Filter by platform id, e.g. \"x\", \"instagram\"."
}
}
}{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"to": {
"type": "string",
"description": "ISO 8601 upper bound on the sort field."
},
"from": {
"type": "string",
"description": "ISO 8601 lower bound on the sort field."
},
"sort": {
"enum": [
"createdAt",
"scheduledFor"
],
"type": "string",
"description": "createdAt (default) or scheduledFor (only posts that have one)."
},
"limit": {
"type": "integer",
"default": 20,
"maximum": 100,
"minimum": 1,
"description": "Page size, 1–100."
},
"order": {
"enum": [
"asc",
"desc"
],
"type": "string"
},
"cursor": {
"type": "string",
"description": "Opaque cursor from the previous page. Omit for the first page."
},
"status": {
"type": "string",
"description": "draft, scheduled, published or failed."
},
"accountId": {
"type": "string",
"description": "Only posts targeting this account."
}
}
}| tiktok settings keyed by tiktok account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| bluesky | object | bluesky settings keyed by bluesky account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| content | string | The caption / body text. |
| post_id | string | A draft from create_post or list_posts. Leave out to create the post in this call. |
| object | facebook settings keyed by facebook account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| hashtags | array | Without the # is fine. |
| cover_key | string | media_key of a cover image for video posts. |
| object | pinterest settings keyed by pinterest account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| wordpress | object | wordpress settings keyed by wordpress account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| media_keys | array | media_key values from upload_media. |
| media_urls | array | Without post_id: public http(s) URLs of the ORIGINAL files to download and attach, up to 100 MB each. |
| publish_at | string | ISO 8601 UTC, at least 10 minutes ahead. Either this or publish_now. |
| account_ids | array | Where this post goes: each account's ref from list_accounts (e.g. "x:acme_studio"), or its id. Prefer the ref; it is what the person reads when approving. Always the complete list, unlike the per-account maps: an account left out is removed from the post, with its text and settings. |
| description | string |
| publish_now | boolean | true to publish immediately. Either this or publish_at. |
| idempotency_key | string | Without post_id: any unique string; a retry with the same key never makes a second post. |
| cover_by_account | object | One account's own video cover, keyed by account ref or id: a media_key, or null to use the post's cover. |
| media_by_account | object | One account's own media, keyed by account ref or id: its whole ordered list of media_key values; [] posts no media there, null goes back to the post's media. |
| account_overrides | object | Text for one account, keyed by account ref or id: title, caption, hashtags. Send only the accounts you are changing; every other account keeps its text. An entry is that account's whole text (a field it leaves out is cleared); null clears the account's text. Wins over channel_overrides and the base text. |
| channel_overrides | object | Text for every account of one platform, keyed by platform id (e.g. "instagram"): "the Instagram caption". Send only the platforms you are changing; null clears the text of that platform's accounts. An account_overrides entry for the same account wins field by field. |
| cover_timestamp_ms | integer | The video frame, in milliseconds, used as the cover when no cover image is set. |
| publish_at_overrides | object | A different publish time for one account, keyed by account ref or id (ISO 8601 UTC). |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"type": {
"enum": [
"text",
"image",
"video",
"blog"
],
"type": "string",
"description": "Required without post_id: the type the user asked for. Ask if they have not said. blog is a WordPress blog post."
},
"title": {
"type": "string",
"maxLength": 200,
"description": "Title, used by platforms that have one (YouTube, Pinterest). Required for a blog post, where it becomes the WordPress page heading."
},
"tiktok": {
"type": "object",
"description": "tiktok settings keyed by tiktok account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"properties": {
"delivery": {
"enum": [
"direct_post"
],
"type": "string",
"description": "[optional] direct_post publishes the post to the account; it is the only value. Default direct_post."
},
"interactions": {
"type": "object",
"required": [
"comment",
"duet",
"stitch"
],
"properties": {
"duet": {
"type": "boolean"
},
"stitch": {
"type": "boolean"
},
"comment": {
"type": "boolean"
}
},
"description": "[account default] Allow comments, duets and stitches on this post. Default: the switches saved on the account. Anything the creator turned off in TikTok stays off. Send only when the user asks."
},
"brand_content": {
"type": "boolean",
"description": "[optional] Branded content for someone else. Needs commercial_content; cannot be SELF_ONLY. Default false."
},
"brand_organic": {
"type": "boolean",
"description": "[optional] Promotes the creator's own brand. Needs commercial_content. Default false."
},
"privacy_level": {
"enum": [
"PUBLIC_TO_EVERYONE",
"MUTUAL_FOLLOW_FRIENDS",
"FOLLOWER_OF_CREATOR",
"SELF_ONLY"
],
"type": "string",
"description": "[optional] Who can see the post. SELF_ONLY when the user says \"only me\". Default PUBLIC_TO_EVERYONE."
},
"commercial_content": {
"type": "boolean",
"description": "[optional] The post promotes a brand, product or service. Default false."
}
}
}
},
"bluesky": {
"type": "object",
"description": "bluesky settings keyed by bluesky account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"alt_text",
"labels"
],
"properties": {
"labels": {
"type": "array",
"items": {
"enum": [
"sexual",
"nudity",
"porn",
"graphic-media"
],
"type": "string"
},
"description": "[optional] Content warnings: at most one of sexual (Suggestive), nudity, porn (Adult), plus graphic-media. Only on posts with images or video. Default []."
},
"alt_text": {
"type": "array",
"items": {
"type": "string"
},
"description": "[optional] One description per file, in the order this account publishes its media; \"\" leaves a file undescribed. Exactly as many entries as files. Up to 2000 characters each. Default: no alt text."
}
}
}
},
"content": {
"type": "string",
"maxLength": 100000,
"description": "The caption / body text."
},
"post_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "A draft from create_post or list_posts. Leave out to create the post in this call."
},
"facebook": {
"type": "object",
"description": "facebook settings keyed by facebook account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"video_as"
],
"properties": {
"video_as": {
"type": "string",
"const": "reel",
"description": "[optional] Publish a video as a Reel: 3 to 90 seconds, vertical 9:16, at most 30 Reels per Page per day. Leave the account out for a classic Page video (the default)."
}
}
}
},
"hashtags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Without the # is fine."
},
"cover_key": {
"type": "string",
"description": "media_key of a cover image for video posts."
},
"pinterest": {
"type": "object",
"description": "pinterest settings keyed by pinterest account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"board_id"
],
"properties": {
"link": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "[optional] The Pin's clickable destination. A URL in the caption is never clickable on Pinterest. Default none."
},
"alt_text": {
"type": "string",
"maxLength": 500,
"description": "[optional] Describes the image for screen readers. Default none; the title is never used."
},
"board_id": {
"type": "string",
"pattern": "^\\d+$",
"description": "[account default] The board the Pin goes on. Default: the account's saved board, and no account has one yet, so every Pin needs this: ask the user which board, from list_options req_type list_board."
}
}
}
},
"wordpress": {
"type": "object",
"description": "wordpress settings keyed by wordpress account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"properties": {
"seo": {
"type": "object",
"properties": {
"title": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"minLength": 1
},
"focus_keyphrase": {
"type": "string",
"minLength": 1
}
},
"description": "[optional] Yoast SEO fields. Only for an account whose list_accounts entry shows seo \"yoast\". Default: Yoast's own."
},
"slug": {
"type": "string",
"maxLength": 200,
"minLength": 1,
"description": "[optional] The URL slug. Default: made from the title."
},
"tags": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "[optional] Tag names; a new one is created. Only for a post type that has tags. Default none."
},
"terms": {
"type": "object",
"description": "[optional] Terms of the post type's own taxonomies, as { taxonomy slug: [names] }, e.g. { \"genre\": [\"Romance\"] }. list_post_type names each type's taxonomies. Names must already exist on the site; an unknown one is refused at schedule with the site's list. Default none.",
"propertyNames": {
"type": "string",
"pattern": "^[a-z0-9_-]{1,32}$"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1
}
},
"status": {
"enum": [
"publish",
"draft"
],
"type": "string",
"description": "[optional] publish, or draft to review it in wp-admin. Default publish."
},
"excerpt": {
"type": "string",
"minLength": 1,
"description": "[optional] Short summary for feeds and link previews. Default: WordPress makes one from the body."
},
"post_type": {
"type": "string",
"pattern": "^[a-z0-9_-]{1,20}$",
"description": "[optional] The post type to publish as, e.g. a short story instead of a blog post: a slug from list_options req_type list_post_type. Default post."
},
"categories": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "[optional] Category names that already exist on the site; an unknown one is refused at schedule with the site's list. Only for a post type that has categories. Default: the site's default category."
},
"featured_image_alt": {
"type": "string",
"minLength": 1,
"description": "[optional] Alt text for the featured image (the first image). Default none."
}
}
}
},
"media_keys": {
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20,
"description": "media_key values from upload_media."
},
"media_urls": {
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20,
"description": "Without post_id: public http(s) URLs of the ORIGINAL files to download and attach, up to 100 MB each."
},
"publish_at": {
"type": "string",
"description": "ISO 8601 UTC, at least 10 minutes ahead. Either this or publish_now."
},
"account_ids": {
"type": "array",
"items": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"maxItems": 10,
"minItems": 1,
"description": "Where this post goes: each account's ref from list_accounts (e.g. \"x:acme_studio\"), or its id. Prefer the ref; it is what the person reads when approving. Always the complete list, unlike the per-account maps: an account left out is removed from the post, with its text and settings."
},
"description": {
"type": "string"
},
"publish_now": {
"type": "boolean",
"description": "true to publish immediately. Either this or publish_at."
},
"idempotency_key": {
"type": "string",
"maxLength": 200,
"description": "Without post_id: any unique string; a retry with the same key never makes a second post."
},
"cover_by_account": {
"type": "object",
"description": "One account's own video cover, keyed by account ref or id: a media_key, or null to use the post's cover.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "string",
"maxLength": 512
},
{
"type": "null"
}
]
}
},
"media_by_account": {
"type": "object",
"description": "One account's own media, keyed by account ref or id: its whole ordered list of media_key values; [] posts no media there, null goes back to the post's media.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20
},
{
"type": "null"
}
]
}
},
"account_overrides": {
"type": "object",
"description": "Text for one account, keyed by account ref or id: title, caption, hashtags. Send only the accounts you are changing; every other account keeps its text. An entry is that account's whole text (a field it leaves out is cleared); null clears the account's text. Wins over channel_overrides and the base text.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 10000
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"maxItems": 30
},
"description": {
"type": "string",
"description": "This account's caption. \"\" posts no text on this account."
}
}
},
{
"type": "null"
}
]
}
},
"channel_overrides": {
"type": "object",
"description": "Text for every account of one platform, keyed by platform id (e.g. \"instagram\"): \"the Instagram caption\". Send only the platforms you are changing; null clears the text of that platform's accounts. An account_overrides entry for the same account wins field by field.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 10000
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"maxItems": 30
},
"description": {
"type": "string",
"description": "This account's caption. \"\" posts no text on this account."
}
}
},
{
"type": "null"
}
]
}
},
"cover_timestamp_ms": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "The video frame, in milliseconds, used as the cover when no cover image is set."
},
"publish_at_overrides": {
"type": "object",
"description": "A different publish time for one account, keyed by account ref or id (ISO 8601 UTC).",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "string"
}
}
}
}| bluesky | object | bluesky settings keyed by bluesky account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| content | string | The caption / body text. |
| post_id* | string |
| object | facebook settings keyed by facebook account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| hashtags | array | Without the # is fine. |
| cover_key | string | null | null clears the cover. |
| object | pinterest settings keyed by pinterest account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| wordpress | object | wordpress settings keyed by wordpress account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own. |
| media_keys | any | Replaces the media; null clears it. |
| publish_at | string | ISO 8601 UTC. On a scheduled post: its new time, at least 10 minutes ahead. On a draft: the time it keeps until schedule_post. |
| account_ids | array | Where this post goes: each account's ref from list_accounts (e.g. "x:acme_studio"), or its id. Prefer the ref; it is what the person reads when approving. Always the complete list, unlike the per-account maps: an account left out is removed from the post, with its text and settings. |
| description | string |
| cover_by_account | object | One account's own video cover, keyed by account ref or id: a media_key, or null to use the post's cover. |
| media_by_account | object | One account's own media, keyed by account ref or id: its whole ordered list of media_key values; [] posts no media there, null goes back to the post's media. |
| account_overrides | object | Text for one account, keyed by account ref or id: title, caption, hashtags. Send only the accounts you are changing; every other account keeps its text. An entry is that account's whole text (a field it leaves out is cleared); null clears the account's text. Wins over channel_overrides and the base text. |
| channel_overrides | object | Text for every account of one platform, keyed by platform id (e.g. "instagram"): "the Instagram caption". Send only the platforms you are changing; null clears the text of that platform's accounts. An account_overrides entry for the same account wins field by field. |
| cover_timestamp_ms | integer | The video frame, in milliseconds, used as the cover when no cover image is set. |
| publish_at_overrides | object | Scheduled posts only: a different time for one account, keyed by account ref or id (ISO 8601 UTC). |
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"post_id"
],
"properties": {
"type": {
"enum": [
"text",
"image",
"video",
"story",
"blog"
],
"type": "string",
"description": "Post type. Keep the type the draft already has unless the user asked to change it."
},
"title": {
"type": "string",
"maxLength": 200,
"description": "Title, used by platforms that have one (YouTube, Pinterest). Required for a blog post, where it becomes the WordPress page heading."
},
"tiktok": {
"type": "object",
"description": "tiktok settings keyed by tiktok account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"properties": {
"delivery": {
"enum": [
"direct_post"
],
"type": "string",
"description": "[optional] direct_post publishes the post to the account; it is the only value. Default direct_post."
},
"interactions": {
"type": "object",
"required": [
"comment",
"duet",
"stitch"
],
"properties": {
"duet": {
"type": "boolean"
},
"stitch": {
"type": "boolean"
},
"comment": {
"type": "boolean"
}
},
"description": "[account default] Allow comments, duets and stitches on this post. Default: the switches saved on the account. Anything the creator turned off in TikTok stays off. Send only when the user asks."
},
"brand_content": {
"type": "boolean",
"description": "[optional] Branded content for someone else. Needs commercial_content; cannot be SELF_ONLY. Default false."
},
"brand_organic": {
"type": "boolean",
"description": "[optional] Promotes the creator's own brand. Needs commercial_content. Default false."
},
"privacy_level": {
"enum": [
"PUBLIC_TO_EVERYONE",
"MUTUAL_FOLLOW_FRIENDS",
"FOLLOWER_OF_CREATOR",
"SELF_ONLY"
],
"type": "string",
"description": "[optional] Who can see the post. SELF_ONLY when the user says \"only me\". Default PUBLIC_TO_EVERYONE."
},
"commercial_content": {
"type": "boolean",
"description": "[optional] The post promotes a brand, product or service. Default false."
}
}
}
},
"bluesky": {
"type": "object",
"description": "bluesky settings keyed by bluesky account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"alt_text",
"labels"
],
"properties": {
"labels": {
"type": "array",
"items": {
"enum": [
"sexual",
"nudity",
"porn",
"graphic-media"
],
"type": "string"
},
"description": "[optional] Content warnings: at most one of sexual (Suggestive), nudity, porn (Adult), plus graphic-media. Only on posts with images or video. Default []."
},
"alt_text": {
"type": "array",
"items": {
"type": "string"
},
"description": "[optional] One description per file, in the order this account publishes its media; \"\" leaves a file undescribed. Exactly as many entries as files. Up to 2000 characters each. Default: no alt text."
}
}
}
},
"content": {
"type": "string",
"maxLength": 100000,
"description": "The caption / body text."
},
"post_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"facebook": {
"type": "object",
"description": "facebook settings keyed by facebook account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"video_as"
],
"properties": {
"video_as": {
"type": "string",
"const": "reel",
"description": "[optional] Publish a video as a Reel: 3 to 90 seconds, vertical 9:16, at most 30 Reels per Page per day. Leave the account out for a classic Page video (the default)."
}
}
}
},
"hashtags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Without the # is fine."
},
"cover_key": {
"type": [
"string",
"null"
],
"description": "null clears the cover."
},
"pinterest": {
"type": "object",
"description": "pinterest settings keyed by pinterest account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"required": [
"board_id"
],
"properties": {
"link": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "[optional] The Pin's clickable destination. A URL in the caption is never clickable on Pinterest. Default none."
},
"alt_text": {
"type": "string",
"maxLength": 500,
"description": "[optional] Describes the image for screen readers. Default none; the title is never used."
},
"board_id": {
"type": "string",
"pattern": "^\\d+$",
"description": "[account default] The board the Pin goes on. Default: the account's saved board, and no account has one yet, so every Pin needs this: ask the user which board, from list_options req_type list_board."
}
}
}
},
"wordpress": {
"type": "object",
"description": "wordpress settings keyed by wordpress account (ref or id). Each field says whether it is required, an account default or optional. Send only the accounts you are changing: an entry is that account's whole settings, and every other account keeps its own.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "object",
"properties": {
"seo": {
"type": "object",
"properties": {
"title": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"minLength": 1
},
"focus_keyphrase": {
"type": "string",
"minLength": 1
}
},
"description": "[optional] Yoast SEO fields. Only for an account whose list_accounts entry shows seo \"yoast\". Default: Yoast's own."
},
"slug": {
"type": "string",
"maxLength": 200,
"minLength": 1,
"description": "[optional] The URL slug. Default: made from the title."
},
"tags": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "[optional] Tag names; a new one is created. Only for a post type that has tags. Default none."
},
"terms": {
"type": "object",
"description": "[optional] Terms of the post type's own taxonomies, as { taxonomy slug: [names] }, e.g. { \"genre\": [\"Romance\"] }. list_post_type names each type's taxonomies. Names must already exist on the site; an unknown one is refused at schedule with the site's list. Default none.",
"propertyNames": {
"type": "string",
"pattern": "^[a-z0-9_-]{1,32}$"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1
}
},
"status": {
"enum": [
"publish",
"draft"
],
"type": "string",
"description": "[optional] publish, or draft to review it in wp-admin. Default publish."
},
"excerpt": {
"type": "string",
"minLength": 1,
"description": "[optional] Short summary for feeds and link previews. Default: WordPress makes one from the body."
},
"post_type": {
"type": "string",
"pattern": "^[a-z0-9_-]{1,20}$",
"description": "[optional] The post type to publish as, e.g. a short story instead of a blog post: a slug from list_options req_type list_post_type. Default post."
},
"categories": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"description": "[optional] Category names that already exist on the site; an unknown one is refused at schedule with the site's list. Only for a post type that has categories. Default: the site's default category."
},
"featured_image_alt": {
"type": "string",
"minLength": 1,
"description": "[optional] Alt text for the featured image (the first image). Default none."
}
}
}
},
"media_keys": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20
},
{
"type": "null"
}
],
"description": "Replaces the media; null clears it."
},
"publish_at": {
"type": "string",
"description": "ISO 8601 UTC. On a scheduled post: its new time, at least 10 minutes ahead. On a draft: the time it keeps until schedule_post."
},
"account_ids": {
"type": "array",
"items": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"maxItems": 10,
"minItems": 1,
"description": "Where this post goes: each account's ref from list_accounts (e.g. \"x:acme_studio\"), or its id. Prefer the ref; it is what the person reads when approving. Always the complete list, unlike the per-account maps: an account left out is removed from the post, with its text and settings."
},
"description": {
"type": "string"
},
"cover_by_account": {
"type": "object",
"description": "One account's own video cover, keyed by account ref or id: a media_key, or null to use the post's cover.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "string",
"maxLength": 512
},
{
"type": "null"
}
]
}
},
"media_by_account": {
"type": "object",
"description": "One account's own media, keyed by account ref or id: its whole ordered list of media_key values; [] posts no media there, null goes back to the post's media.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string",
"maxLength": 512
},
"maxItems": 20
},
{
"type": "null"
}
]
}
},
"account_overrides": {
"type": "object",
"description": "Text for one account, keyed by account ref or id: title, caption, hashtags. Send only the accounts you are changing; every other account keeps its text. An entry is that account's whole text (a field it leaves out is cleared); null clears the account's text. Wins over channel_overrides and the base text.",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"anyOf": [
{
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 10000
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"maxItems": 30
},
"description": {
"type": "string",
"description": "This account's caption. \"\" posts no text on this account."
}
}
},
{
"type": "null"
}
]
}
},
"channel_overrides": {
"type": "object",
"description": "Text for every account of one platform, keyed by platform id (e.g. \"instagram\"): \"the Instagram caption\". Send only the platforms you are changing; null clears the text of that platform's accounts. An account_overrides entry for the same account wins field by field.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 10000
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"maxItems": 30
},
"description": {
"type": "string",
"description": "This account's caption. \"\" posts no text on this account."
}
}
},
{
"type": "null"
}
]
}
},
"cover_timestamp_ms": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "The video frame, in milliseconds, used as the cover when no cover image is set."
},
"publish_at_overrides": {
"type": "object",
"description": "Scheduled posts only: a different time for one account, keyed by account ref or id (ISO 8601 UTC).",
"propertyNames": {
"type": "string",
"maxLength": 300,
"minLength": 1
},
"additionalProperties": {
"type": "string"
}
}
}
}{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"url": {
"type": "string",
"description": "Public http(s) URL of the original file. Either this, file or base64."
},
"file": {
"type": "object",
"required": [
"download_url",
"file_id"
],
"properties": {
"file_id": {
"type": "string"
},
"file_name": {
"type": "string"
},
"mime_type": {
"type": "string"
},
"download_url": {
"type": "string"
}
},
"description": "Filled by the chat app (ChatGPT) with the attachment's https download_url; never a local path. Either this, url or base64.",
"additionalProperties": false
},
"base64": {
"type": "string",
"description": "The original file bytes as base64, read from disk (a data: URI is fine). Either this, file or url."
},
"filename": {
"type": "string"
},
"content_type": {
"type": "string",
"description": "MIME type, e.g. image/jpeg. Required with base64."
}
}
}